Compare commits
	
		
			32 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e44c260430 | |||
| 426c18bfef | |||
| 757ff3a7c7 | |||
| 25cb269334 | |||
| ce00b327bb | |||
| 7e52c5a532 | |||
| 3d7428e76f | |||
| cb3c17c5db | |||
| 21e17a294e | |||
| b3fddef4e0 | |||
| b12cf2c4f1 | |||
| 5a1987f77b | |||
| f1cfb326d5 | |||
| 7299157521 | |||
| c6f88e8d85 | |||
| f87f6c75f5 | |||
| ca0f9f4e59 | |||
| c8451c07aa | |||
| 5f3060995b | |||
| 230abf08e8 | |||
| 5f8d8873b3 | |||
| 81be7d477b | |||
| 350dd4bb0e | |||
| c1df87582d | |||
| 350b4b0d21 | |||
| 23f59ff98a | |||
| b2b4532a4f | |||
| 54bbeade8c | |||
| da1eba4676 | |||
| c7830c3f17 | |||
| 7179e44814 | |||
| 62303209f8 | 
					 15 changed files with 1600 additions and 843 deletions
				
			
		
							
								
								
									
										7
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -1,2 +1,9 @@
 | 
			
		|||
# ESLINT report
 | 
			
		||||
eslint_report.html
 | 
			
		||||
 | 
			
		||||
# Demo sources
 | 
			
		||||
demos/src_highres
 | 
			
		||||
demos/src_lowres
 | 
			
		||||
 | 
			
		||||
# Local dev environment settings
 | 
			
		||||
.vscode
 | 
			
		||||
							
								
								
									
										13
									
								
								Pipfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Pipfile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
[[source]]
 | 
			
		||||
url = "https://pypi.org/simple"
 | 
			
		||||
verify_ssl = true
 | 
			
		||||
name = "pypi"
 | 
			
		||||
 | 
			
		||||
[packages]
 | 
			
		||||
 | 
			
		||||
[dev-packages]
 | 
			
		||||
black = "*"
 | 
			
		||||
prospector = {extras = ["with_mypy"], version = "*"}
 | 
			
		||||
 | 
			
		||||
[requires]
 | 
			
		||||
python_version = "3.11"
 | 
			
		||||
							
								
								
									
										509
									
								
								Pipfile.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										509
									
								
								Pipfile.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,509 @@
 | 
			
		|||
{
 | 
			
		||||
    "_meta": {
 | 
			
		||||
        "hash": {
 | 
			
		||||
            "sha256": "6bf84a05ccc0682985bf11d77db01419dde589c614d97f59ca2bac4c4b43c80f"
 | 
			
		||||
        },
 | 
			
		||||
        "pipfile-spec": 6,
 | 
			
		||||
        "requires": {
 | 
			
		||||
            "python_version": "3.11"
 | 
			
		||||
        },
 | 
			
		||||
        "sources": [
 | 
			
		||||
            {
 | 
			
		||||
                "name": "pypi",
 | 
			
		||||
                "url": "https://pypi.org/simple",
 | 
			
		||||
                "verify_ssl": true
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    "default": {},
 | 
			
		||||
    "develop": {
 | 
			
		||||
        "astroid": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324",
 | 
			
		||||
                "sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_full_version >= '3.7.2'",
 | 
			
		||||
            "version": "==2.15.5"
 | 
			
		||||
        },
 | 
			
		||||
        "black": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
 | 
			
		||||
                "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915",
 | 
			
		||||
                "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326",
 | 
			
		||||
                "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940",
 | 
			
		||||
                "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b",
 | 
			
		||||
                "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30",
 | 
			
		||||
                "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c",
 | 
			
		||||
                "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c",
 | 
			
		||||
                "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab",
 | 
			
		||||
                "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27",
 | 
			
		||||
                "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2",
 | 
			
		||||
                "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961",
 | 
			
		||||
                "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9",
 | 
			
		||||
                "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb",
 | 
			
		||||
                "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70",
 | 
			
		||||
                "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331",
 | 
			
		||||
                "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2",
 | 
			
		||||
                "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266",
 | 
			
		||||
                "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d",
 | 
			
		||||
                "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6",
 | 
			
		||||
                "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b",
 | 
			
		||||
                "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925",
 | 
			
		||||
                "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8",
 | 
			
		||||
                "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4",
 | 
			
		||||
                "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==23.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "click": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
 | 
			
		||||
                "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==8.1.3"
 | 
			
		||||
        },
 | 
			
		||||
        "colorama": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
 | 
			
		||||
                "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "platform_system == 'Windows'",
 | 
			
		||||
            "version": "==0.4.6"
 | 
			
		||||
        },
 | 
			
		||||
        "dill": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
 | 
			
		||||
                "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.11'",
 | 
			
		||||
            "version": "==0.3.6"
 | 
			
		||||
        },
 | 
			
		||||
        "dodgy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a",
 | 
			
		||||
                "sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.2.1"
 | 
			
		||||
        },
 | 
			
		||||
        "flake8": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db",
 | 
			
		||||
                "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_full_version >= '3.6.1'",
 | 
			
		||||
            "version": "==5.0.4"
 | 
			
		||||
        },
 | 
			
		||||
        "flake8-polyfill": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9",
 | 
			
		||||
                "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "gitdb": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a",
 | 
			
		||||
                "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==4.0.10"
 | 
			
		||||
        },
 | 
			
		||||
        "gitpython": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573",
 | 
			
		||||
                "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==3.1.31"
 | 
			
		||||
        },
 | 
			
		||||
        "isort": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
 | 
			
		||||
                "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_full_version >= '3.8.0'",
 | 
			
		||||
            "version": "==5.12.0"
 | 
			
		||||
        },
 | 
			
		||||
        "lazy-object-proxy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382",
 | 
			
		||||
                "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82",
 | 
			
		||||
                "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9",
 | 
			
		||||
                "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494",
 | 
			
		||||
                "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46",
 | 
			
		||||
                "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30",
 | 
			
		||||
                "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63",
 | 
			
		||||
                "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4",
 | 
			
		||||
                "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae",
 | 
			
		||||
                "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be",
 | 
			
		||||
                "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701",
 | 
			
		||||
                "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd",
 | 
			
		||||
                "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006",
 | 
			
		||||
                "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a",
 | 
			
		||||
                "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586",
 | 
			
		||||
                "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8",
 | 
			
		||||
                "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821",
 | 
			
		||||
                "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07",
 | 
			
		||||
                "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b",
 | 
			
		||||
                "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171",
 | 
			
		||||
                "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b",
 | 
			
		||||
                "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2",
 | 
			
		||||
                "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7",
 | 
			
		||||
                "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4",
 | 
			
		||||
                "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8",
 | 
			
		||||
                "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e",
 | 
			
		||||
                "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f",
 | 
			
		||||
                "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda",
 | 
			
		||||
                "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4",
 | 
			
		||||
                "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e",
 | 
			
		||||
                "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671",
 | 
			
		||||
                "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11",
 | 
			
		||||
                "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455",
 | 
			
		||||
                "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734",
 | 
			
		||||
                "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb",
 | 
			
		||||
                "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==1.9.0"
 | 
			
		||||
        },
 | 
			
		||||
        "mccabe": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
 | 
			
		||||
                "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==0.7.0"
 | 
			
		||||
        },
 | 
			
		||||
        "mypy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0cf0ca95e4b8adeaf07815a78b4096b65adf64ea7871b39a2116c19497fcd0dd",
 | 
			
		||||
                "sha256:0f98973e39e4a98709546a9afd82e1ffcc50c6ec9ce6f7870f33ebbf0bd4f26d",
 | 
			
		||||
                "sha256:19d42b08c7532d736a7e0fb29525855e355fa51fd6aef4f9bbc80749ff64b1a2",
 | 
			
		||||
                "sha256:210fe0f39ec5be45dd9d0de253cb79245f0a6f27631d62e0c9c7988be7152965",
 | 
			
		||||
                "sha256:3b1b5c875fcf3e7217a3de7f708166f641ca154b589664c44a6fd6d9f17d9e7e",
 | 
			
		||||
                "sha256:3f2b353eebef669529d9bd5ae3566905a685ae98b3af3aad7476d0d519714758",
 | 
			
		||||
                "sha256:50f65f0e9985f1e50040e603baebab83efed9eb37e15a22a4246fa7cd660f981",
 | 
			
		||||
                "sha256:53c2a1fed81e05ded10a4557fe12bae05b9ecf9153f162c662a71d924d504135",
 | 
			
		||||
                "sha256:5a0ee54c2cb0f957f8a6f41794d68f1a7e32b9968675ade5846f538504856d42",
 | 
			
		||||
                "sha256:62bf18d97c6b089f77f0067b4e321db089d8520cdeefc6ae3ec0f873621c22e5",
 | 
			
		||||
                "sha256:653863c75f0dbb687d92eb0d4bd9fe7047d096987ecac93bb7b1bc336de48ebd",
 | 
			
		||||
                "sha256:67242d5b28ed0fa88edd8f880aed24da481929467fdbca6487167cb5e3fd31ff",
 | 
			
		||||
                "sha256:6ba9a69172abaa73910643744d3848877d6aac4a20c41742027dcfd8d78f05d9",
 | 
			
		||||
                "sha256:6c34d43e3d54ad05024576aef28081d9d0580f6fa7f131255f54020eb12f5352",
 | 
			
		||||
                "sha256:7461469e163f87a087a5e7aa224102a30f037c11a096a0ceeb721cb0dce274c8",
 | 
			
		||||
                "sha256:94a81b9354545123feb1a99b960faeff9e1fa204fce47e0042335b473d71530d",
 | 
			
		||||
                "sha256:a0b2e0da7ff9dd8d2066d093d35a169305fc4e38db378281fce096768a3dbdbf",
 | 
			
		||||
                "sha256:a34eed094c16cad0f6b0d889811592c7a9b7acf10d10a7356349e325d8704b4f",
 | 
			
		||||
                "sha256:a3af348e0925a59213244f28c7c0c3a2c2088b4ba2fe9d6c8d4fbb0aba0b7d05",
 | 
			
		||||
                "sha256:b4c734d947e761c7ceb1f09a98359dd5666460acbc39f7d0a6b6beec373c5840",
 | 
			
		||||
                "sha256:bba57b4d2328740749f676807fcf3036e9de723530781405cc5a5e41fc6e20de",
 | 
			
		||||
                "sha256:ca33ab70a4aaa75bb01086a0b04f0ba8441e51e06fc57e28585176b08cad533b",
 | 
			
		||||
                "sha256:de1e7e68148a213036276d1f5303b3836ad9a774188961eb2684eddff593b042",
 | 
			
		||||
                "sha256:f051ca656be0c179c735a4c3193f307d34c92fdc4908d44fd4516fbe8b10567d",
 | 
			
		||||
                "sha256:f5984a8d13d35624e3b235a793c814433d810acba9eeefe665cdfed3d08bc3af",
 | 
			
		||||
                "sha256:f7a5971490fd4a5a436e143105a1f78fa8b3fe95b30fff2a77542b4f3227a01f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.4.0"
 | 
			
		||||
        },
 | 
			
		||||
        "mypy-extensions": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
 | 
			
		||||
                "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.5'",
 | 
			
		||||
            "version": "==1.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "packaging": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
 | 
			
		||||
                "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==23.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pathspec": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
 | 
			
		||||
                "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==0.11.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pep8-naming": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5d9f1056cb9427ce344e98d1a7f5665710e2f20f748438e308995852cfa24164",
 | 
			
		||||
                "sha256:f3b4a5f9dd72b991bf7d8e2a341d2e1aa3a884a769b5aaac4f56825c1763bf3a"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.10.0"
 | 
			
		||||
        },
 | 
			
		||||
        "platformdirs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc",
 | 
			
		||||
                "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==3.8.0"
 | 
			
		||||
        },
 | 
			
		||||
        "prospector": {
 | 
			
		||||
            "extras": [
 | 
			
		||||
                "with_mypy"
 | 
			
		||||
            ],
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3bfe103c28bb821cca84926ca31357fbfd32405e4bf8c34ca2e55885684557e4",
 | 
			
		||||
                "sha256:cc8f09e79bdd32247edddf05b666940e88ad96338a84f5717b1e8c0678337821"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.10.2"
 | 
			
		||||
        },
 | 
			
		||||
        "pycodestyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785",
 | 
			
		||||
                "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==2.9.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pydocstyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019",
 | 
			
		||||
                "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==6.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pyflakes": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
 | 
			
		||||
                "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==2.5.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1",
 | 
			
		||||
                "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_full_version >= '3.7.2'",
 | 
			
		||||
            "version": "==2.17.4"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-celery": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:41e32094e7408d15c044178ea828dd524beedbdbe6f83f712c5e35bde1de4beb"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.3"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-django": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0ac090d106c62fe33782a1d01bda1610b761bb1c9bf5035ced9d5f23a13d8591",
 | 
			
		||||
                "sha256:56b12b6adf56d548412445bd35483034394a1a94901c3f8571980a13882299d5"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.5.3"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-flask": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:f4d97de2216bf7bfce07c9c08b166e978fe9f2725de2a50a9845a97de7e31517"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.6"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint-plugin-utils": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:b3d43e85ab74c4f48bb46ae4ce771e39c3a20f8b3d56982ab17aa73b4f98d535",
 | 
			
		||||
                "sha256:ce48bc0516ae9415dd5c752c940dfe601b18fe0f48aa249f2386adfa95a004dd"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_full_version >= '3.6.2'",
 | 
			
		||||
            "version": "==0.7"
 | 
			
		||||
        },
 | 
			
		||||
        "pyyaml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
 | 
			
		||||
                "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
 | 
			
		||||
                "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
 | 
			
		||||
                "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
 | 
			
		||||
                "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
 | 
			
		||||
                "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
 | 
			
		||||
                "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
 | 
			
		||||
                "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
 | 
			
		||||
                "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
 | 
			
		||||
                "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
 | 
			
		||||
                "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
 | 
			
		||||
                "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
 | 
			
		||||
                "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
 | 
			
		||||
                "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
 | 
			
		||||
                "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
 | 
			
		||||
                "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
 | 
			
		||||
                "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
 | 
			
		||||
                "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
 | 
			
		||||
                "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
 | 
			
		||||
                "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
 | 
			
		||||
                "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
 | 
			
		||||
                "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
 | 
			
		||||
                "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
 | 
			
		||||
                "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
 | 
			
		||||
                "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
 | 
			
		||||
                "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
 | 
			
		||||
                "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
 | 
			
		||||
                "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
 | 
			
		||||
                "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
 | 
			
		||||
                "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
 | 
			
		||||
                "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
 | 
			
		||||
                "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
 | 
			
		||||
                "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
 | 
			
		||||
                "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
 | 
			
		||||
                "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
 | 
			
		||||
                "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
 | 
			
		||||
                "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
 | 
			
		||||
                "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
 | 
			
		||||
                "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
 | 
			
		||||
                "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==6.0"
 | 
			
		||||
        },
 | 
			
		||||
        "requirements-detector": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3642cd7a5b261d79536c36bb7ecacf2adabd902d2e0e42bfb2ba82515da10501",
 | 
			
		||||
                "sha256:d7c60493bf166da3dd59de0e6cb25765e0e32a1931aeae92614034e5786d0bd0"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7' and python_version < '4.0'",
 | 
			
		||||
            "version": "==1.2.2"
 | 
			
		||||
        },
 | 
			
		||||
        "semver": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2a23844ba1647362c7490fe3995a86e097bb590d16f0f32dfc383008f19e4cdf",
 | 
			
		||||
                "sha256:9ec78c5447883c67b97f98c3b6212796708191d22e4ad30f4570f840171cbce1"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==3.0.1"
 | 
			
		||||
        },
 | 
			
		||||
        "setoptconf-tmp": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:76035d5cd1593d38b9056ae12d460eca3aaa34ad05c315b69145e138ba80a745",
 | 
			
		||||
                "sha256:e0480addd11347ba52f762f3c4d8afa3e10ad0affbc53e3ffddc0ca5f27d5778"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.3.1"
 | 
			
		||||
        },
 | 
			
		||||
        "smmap": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
 | 
			
		||||
                "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==5.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "snowballstemmer": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
 | 
			
		||||
                "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "toml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
 | 
			
		||||
                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==0.10.2"
 | 
			
		||||
        },
 | 
			
		||||
        "tomlkit": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171",
 | 
			
		||||
                "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==0.11.8"
 | 
			
		||||
        },
 | 
			
		||||
        "typing-extensions": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26",
 | 
			
		||||
                "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.7'",
 | 
			
		||||
            "version": "==4.6.3"
 | 
			
		||||
        },
 | 
			
		||||
        "wrapt": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0",
 | 
			
		||||
                "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420",
 | 
			
		||||
                "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a",
 | 
			
		||||
                "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c",
 | 
			
		||||
                "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079",
 | 
			
		||||
                "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923",
 | 
			
		||||
                "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f",
 | 
			
		||||
                "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1",
 | 
			
		||||
                "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8",
 | 
			
		||||
                "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86",
 | 
			
		||||
                "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0",
 | 
			
		||||
                "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364",
 | 
			
		||||
                "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e",
 | 
			
		||||
                "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c",
 | 
			
		||||
                "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e",
 | 
			
		||||
                "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c",
 | 
			
		||||
                "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727",
 | 
			
		||||
                "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff",
 | 
			
		||||
                "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e",
 | 
			
		||||
                "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29",
 | 
			
		||||
                "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7",
 | 
			
		||||
                "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72",
 | 
			
		||||
                "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475",
 | 
			
		||||
                "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a",
 | 
			
		||||
                "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317",
 | 
			
		||||
                "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2",
 | 
			
		||||
                "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd",
 | 
			
		||||
                "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640",
 | 
			
		||||
                "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98",
 | 
			
		||||
                "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248",
 | 
			
		||||
                "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e",
 | 
			
		||||
                "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d",
 | 
			
		||||
                "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec",
 | 
			
		||||
                "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1",
 | 
			
		||||
                "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e",
 | 
			
		||||
                "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9",
 | 
			
		||||
                "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92",
 | 
			
		||||
                "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb",
 | 
			
		||||
                "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094",
 | 
			
		||||
                "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46",
 | 
			
		||||
                "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29",
 | 
			
		||||
                "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd",
 | 
			
		||||
                "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705",
 | 
			
		||||
                "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8",
 | 
			
		||||
                "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975",
 | 
			
		||||
                "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb",
 | 
			
		||||
                "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e",
 | 
			
		||||
                "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b",
 | 
			
		||||
                "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418",
 | 
			
		||||
                "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019",
 | 
			
		||||
                "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1",
 | 
			
		||||
                "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba",
 | 
			
		||||
                "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6",
 | 
			
		||||
                "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2",
 | 
			
		||||
                "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3",
 | 
			
		||||
                "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7",
 | 
			
		||||
                "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752",
 | 
			
		||||
                "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416",
 | 
			
		||||
                "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f",
 | 
			
		||||
                "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1",
 | 
			
		||||
                "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc",
 | 
			
		||||
                "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145",
 | 
			
		||||
                "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee",
 | 
			
		||||
                "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a",
 | 
			
		||||
                "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7",
 | 
			
		||||
                "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b",
 | 
			
		||||
                "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653",
 | 
			
		||||
                "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0",
 | 
			
		||||
                "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90",
 | 
			
		||||
                "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29",
 | 
			
		||||
                "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6",
 | 
			
		||||
                "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034",
 | 
			
		||||
                "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09",
 | 
			
		||||
                "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559",
 | 
			
		||||
                "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.11'",
 | 
			
		||||
            "version": "==1.15.0"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										103
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -25,17 +25,19 @@ flowchart TB
 | 
			
		|||
```
 | 
			
		||||
 | 
			
		||||
Melpomene is mainly one JS file and one CSS file. You only need to include them in you web page. They are :
 | 
			
		||||
    + `melpomene.js`
 | 
			
		||||
    + `melpomene.css`
 | 
			
		||||
 | 
			
		||||
+ `melpomene.js`
 | 
			
		||||
 | 
			
		||||
+ `melpomene.css`
 | 
			
		||||
 | 
			
		||||
The JS files expect you to write some very specific HTML tags to be able to work.
 | 
			
		||||
To simplify things, you only need to copy-paste the content of `melpomene.html` into your own page and change a few things :
 | 
			
		||||
+ `data-pages-width` must be set to your comic pages width in px
 | 
			
		||||
+ `data-pages-height` must be set to your comic pages height in px
 | 
			
		||||
 | 
			
		||||
+ You must duplicate the `img` tag for each of you comic page and :
 | 
			
		||||
    + set `url` to the actual URL of your page
 | 
			
		||||
    + set `height` and `width` to the actual image sizes 
 | 
			
		||||
    + set `data-zooms` with the zooms information, like so : `<zoom 1 width>, <zoom 1 height>, <zoom 1 x offset>, <zoom 1 y offset>; <zoom 2 width> ...`
 | 
			
		||||
        + example : `<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.44702,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>`
 | 
			
		||||
    + example : `<img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.4470    2,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>`
 | 
			
		||||
 | 
			
		||||
Because doing this by hand would be very tedious, Melpomene comes with an helper script that allows generating that HTML for you from SVGs.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,9 +47,8 @@ The following limitations are known and will be improved upon :
 | 
			
		|||
 | 
			
		||||
+ Mobile support is currently limited
 | 
			
		||||
+ There are some performences issues
 | 
			
		||||
+ Your comic pages need to be the same size
 | 
			
		||||
 | 
			
		||||
# How to setup Melpomene ?
 | 
			
		||||
# How to use Melpomene ?
 | 
			
		||||
 | 
			
		||||
## Defining the zooms
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,21 +58,21 @@ To do so, you can use your favorite SVG editor. If you don't have one, [Inkscape
 | 
			
		|||
To create the zooms for a comic page, what you need to do is :
 | 
			
		||||
 | 
			
		||||
1. Open your comic page in your editor (make sure to keep the image's original size when importing!)
 | 
			
		||||
    * If your software ask, there is no need to actually import the image, you only need to link it.
 | 
			
		||||
     * If your software ask, there is no need to actually import the image, you only need to link it.
 | 
			
		||||
2. Create a simple rectangle over each zoom you want, in the order you want them to show up
 | 
			
		||||
    * You can set the rectangle's color to be translucent so you can still see the page underneath them!
 | 
			
		||||
    * If you want to change the zoom order, you can change their order in the layer view of your SVG tool if they support it
 | 
			
		||||
     * You can set the rectangle's color to be translucent so you can still see the page underneath them!
 | 
			
		||||
     * If you want to change the zoom order, you can change their order in the layer view of your SVG tool if they support it
 | 
			
		||||
3. Once you are done, save the SGV
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Generating the HTML files
 | 
			
		||||
 | 
			
		||||
Once the SVG for your pages are done, put them in one folder. Then, you can run the zoom generator.
 | 
			
		||||
 | 
			
		||||
You need to open a terminal and then run :
 | 
			
		||||
 | 
			
		||||
+ `python zooms_generator.py <path to the SVG folder> html -p <prefix of the img's url> -e <extension of the img's url>`
 | 
			
		||||
    + For example, if your comic pages are hosted at `static/comic/page_x.jpg`, the svg must be named `page_x.svg`
 | 
			
		||||
    and you must run `python zooms_generator.py <path to the SVG folder> html -p /static/comic/ -e jpg`
 | 
			
		||||
      and you must run `python zooms_generator.py <path to the SVG folder> html -p /static/comic/ -e jpg`
 | 
			
		||||
    + It will generate the following `img` tag : `<img loading="lazy" src="static/comic/page_x.jpg" data-zooms="..."/>`
 | 
			
		||||
 | 
			
		||||
The pages need to be in alphabetical order! It assumes the first page is page 1, the next one is page 2, etc..
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +83,83 @@ If you wish to run a custom generation process, this generator can output a JSON
 | 
			
		|||
 | 
			
		||||
You are now ready to integrate Melpomene in your website!
 | 
			
		||||
 | 
			
		||||
## Advanced usage
 | 
			
		||||
 | 
			
		||||
If you need to do some global scaling / offset of all zooms in HTML (if for example you reuse zooms data for multiple resolutions), you can add the following attributes to the `<div id="reader-pages" ...>` tag :
 | 
			
		||||
 | 
			
		||||
+ `data-global-zoom-offset="<x offset float value>,<y offset float value>"` : offset all positions by the provided x / y values
 | 
			
		||||
    + If they become negative, they get clamped to 0 and width / height get reduced to compensate
 | 
			
		||||
    + If they become greater than the page size, they get clamped to the page size and width / height get reduced to compensate
 | 
			
		||||
+ `data-global-zoom-scale="<float value>"` : scale all positions / sizes by this factor
 | 
			
		||||
 | 
			
		||||
# Developpement
 | 
			
		||||
 | 
			
		||||
## Setting up quality checking for JS
 | 
			
		||||
 | 
			
		||||
Regarding JS, quality checking is done using [eslint](https://eslint.org/).
 | 
			
		||||
 | 
			
		||||
The configuration file is `eslint/eslintrc.json`.
 | 
			
		||||
 | 
			
		||||
To setup eslint, you can either install it on your system reading it's documentation, or use the provided Dockerfile to run it. This requires [docker](https://www.docker.com/).
 | 
			
		||||
 | 
			
		||||
To do so, assuming you are using linux, after installing docker, you can run from this repository root:
 | 
			
		||||
 | 
			
		||||
+ `docker build -t melpomene-eslint eslint/`, once
 | 
			
		||||
 | 
			
		||||
+ `docker run -v .:/melpomene:rw -w /melpomene --user $(id -u):$(id -g) melpomene-eslint`, every time you want to run the analysis
 | 
			
		||||
 | 
			
		||||
You can now open `eslint_report.html` to see the result.
 | 
			
		||||
 | 
			
		||||
## Setting up quality checking for Python
 | 
			
		||||
 | 
			
		||||
Regarding Python, quality checking is done using [prospector](https://prospector.landscape.io/en/master/) using [mypy](https://mypy.readthedocs.io/en/stable/) as an additional checker.
 | 
			
		||||
Auto-formating is done using [Black](https://pypi.org/project/black/)
 | 
			
		||||
 | 
			
		||||
Dependencies are managed not using `pip` but [`pipenv`](https://pipenv.pypa.io/en/latest/).
 | 
			
		||||
 | 
			
		||||
To setup prospector, you need to run:
 | 
			
		||||
 | 
			
		||||
+ Only once, in melpomene's root folder:
 | 
			
		||||
    + Install pipenv: `pip install pipenv`
 | 
			
		||||
    + Install melpomene's root folder: `pipenv install --dev`
 | 
			
		||||
+ Every time to run the quality checking:
 | 
			
		||||
    + `pipenv shell`
 | 
			
		||||
    + `prospector -s veryhigh -w mypy --max-line-length 88 .`
 | 
			
		||||
    + `black .`
 | 
			
		||||
 | 
			
		||||
For VSCode users, you can run those automatically in your IDE. Here is what your .vscode/settings.json file should include :
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
    "files.exclude": {
 | 
			
		||||
        "**/.git": true,
 | 
			
		||||
        "**/.svn": true,
 | 
			
		||||
        "**/.hg": true,
 | 
			
		||||
        "**/CVS": true,
 | 
			
		||||
        "**/.DS_Store": true,
 | 
			
		||||
        "**/Thumbs.db": true,
 | 
			
		||||
        "**/*.pyc": true,
 | 
			
		||||
        "**/__pycache__": true
 | 
			
		||||
    },
 | 
			
		||||
    // Replace by your own venv python path
 | 
			
		||||
    "python.defaultInterpreterPath": "~\\.virtualenvs\\melpomene-ajDJjHHp\\Scripts\\python.exe",
 | 
			
		||||
    "python.formatting.provider": "black",
 | 
			
		||||
    "[python]": {
 | 
			
		||||
        "editor.formatOnType": true,
 | 
			
		||||
    },
 | 
			
		||||
    "python.linting.enabled": true,
 | 
			
		||||
    "python.linting.prospectorEnabled": true,
 | 
			
		||||
    "python.linting.prospectorArgs": [
 | 
			
		||||
        "-s",
 | 
			
		||||
        "veryhigh",
 | 
			
		||||
        "-w",
 | 
			
		||||
        "mypy",
 | 
			
		||||
        "--max-line-length",
 | 
			
		||||
        "88"
 | 
			
		||||
    ],
 | 
			
		||||
    "editor.rulers": [
 | 
			
		||||
        88
 | 
			
		||||
    ],
 | 
			
		||||
```
 | 
			
		||||
# Credits
 | 
			
		||||
 | 
			
		||||
Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html).
 | 
			
		||||
Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html). 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,83 +0,0 @@
 | 
			
		|||
PAGES_ZOOMS = [
 | 
			
		||||
    [1, 2481.0, 1327.1057, 0.0, 0.0],
 | 
			
		||||
    [1, 593.15338, 1076.4635, 0.0, 1364.053],
 | 
			
		||||
    [1, 890.72864, 491.29874, 830.81415, 1751.5],
 | 
			
		||||
    [1, 2481.0, 1078.4192, 0.0, 1364.053],
 | 
			
		||||
    [1, 562.77032, 909.44702, 102.48115, 2491.6567],
 | 
			
		||||
    [1, 920.74463, 909.44702, 698.55927, 2491.6567],
 | 
			
		||||
    [1, 728.776, 909.44702, 1652.3695, 2491.6567],
 | 
			
		||||
    [2, 1459.9161, 960.62878, 99.857468, 103.85177],
 | 
			
		||||
    [2, 788.87384, 960.62878, 1593.7252, 103.85177],
 | 
			
		||||
    [2, 455.35007, 305.56384, 389.44412, 1376.0359],
 | 
			
		||||
    [2, 2282.7415, 760.914, 99.857475, 1114.4093],
 | 
			
		||||
    [2, 1069.9728, 496.29166, 101.85461, 1928.2478],
 | 
			
		||||
    [2, 1209.7733, 496.29166, 1172.3267, 1928.2478],
 | 
			
		||||
    [2, 788.87402, 926.67731, 101.85462, 2474.468],
 | 
			
		||||
    [2, 415.40707, 926.67731, 924.68018, 2474.468],
 | 
			
		||||
    [2, 1008.5604, 926.67731, 1372.0416, 2474.468],
 | 
			
		||||
    [3, 2278.4634, 1424.0264, 103.9937, 103.9937],
 | 
			
		||||
    [3, 457.3472, 589.15906, 816.83411, 469.33011],
 | 
			
		||||
    [3, 618.69055, 832.04285, 104.47829, 1578.7172],
 | 
			
		||||
    [3, 1170.7311, 832.04285, 756.71887, 1578.7172],
 | 
			
		||||
    [3, 418.14838, 832.04285, 1962.6545, 1578.7172],
 | 
			
		||||
    [3, 2280.6614, 940.57422, 100.82664, 2461.1147],
 | 
			
		||||
    [4, 1128.3893, 547.21899, 100.85604, 101.85461],
 | 
			
		||||
    [4, 1125.3937, 505.27878, 101.85462, 700.99945],
 | 
			
		||||
    [4, 659.59674, 1104.1381, 1237.9126, 100.46677],
 | 
			
		||||
    [4, 474.09705, 1104.7645, 1908.0801, 103.04887],
 | 
			
		||||
    [4, 991.58466, 641.08496, 365.47833, 1256.2069],
 | 
			
		||||
    [4, 2276.7502, 1268.1899, 101.85461, 1256.2069],
 | 
			
		||||
    [4, 669.66565, 827.24689, 100.95136, 2574.8799],
 | 
			
		||||
    [4, 1574.1992, 825.24969, 806.56573, 2576.877],
 | 
			
		||||
    [5, 2270.7588, 1348.0758, 107.84606, 109.84322],
 | 
			
		||||
    [5, 828.80096, 1879.4762, 104.70337, 1519.7256],
 | 
			
		||||
    [5, 606.50098, 1877.479, 975.78717, 1521.7228],
 | 
			
		||||
    [5, 755.02411, 1139.5406, 1623.401, 1521.7228],
 | 
			
		||||
    [5, 757.02124, 680.4198, 1621.4038, 2716.7849],
 | 
			
		||||
    [6, 2273.6387, 1248.3829, 104.50265, 107.32705],
 | 
			
		||||
    [6, 624.19147, 440.60574, 1259.6805, 771.06006],
 | 
			
		||||
    [6, 830.37231, 940.5238, 110.15143, 1415.0222],
 | 
			
		||||
    [6, 1395.2515, 1313.3441, 985.71411, 1409.3734],
 | 
			
		||||
    [6, 1321.8173, 796.47961, 112.97583, 2417.6829],
 | 
			
		||||
    [6, 974.4165, 754.11365, 1403.7247, 2646.4587],
 | 
			
		||||
    [7, 828.81702, 497.29019, 443.36716, 531.2417],
 | 
			
		||||
    [7, 2275.0127, 1188.0789, 105.30553, 109.78442],
 | 
			
		||||
    [7, 2275.0127, 804.28339, 105.30553, 1356.3481],
 | 
			
		||||
    [7, 847.87823, 1182.9146, 105.30553, 2210.6436],
 | 
			
		||||
    [7, 497.6918, 1185.7389, 994.37964, 2213.468],
 | 
			
		||||
    [7, 854.35431, 1185.7389, 1528.7885, 2213.468],
 | 
			
		||||
    [8, 2272.7561, 1228.2468, 101.85461, 107.84607],
 | 
			
		||||
    [8, 497.29019, 756.91962, 994.58044, 569.18756],
 | 
			
		||||
    [8, 2276.7502, 1052.4977, 101.85461, 1336.0929],
 | 
			
		||||
    [8, 2278.7473, 958.63165, 101.85462, 2444.5107],
 | 
			
		||||
    [9, 1131.1705, 453.31552, 101.67825, 103.09045],
 | 
			
		||||
    [9, 1148.1168, 453.31552, 1232.8488, 103.09045],
 | 
			
		||||
    [9, 1481.6407, 453.31552, 501.89209, 103.09045],
 | 
			
		||||
    [9, 2282.1118, 855.79193, 98.853851, 604.42072],
 | 
			
		||||
    [9, 1776.5449, 881.21149, 290.91275, 1355.71],
 | 
			
		||||
    [9, 703.27454, 398.23981, 762.58685, 2863.9373],
 | 
			
		||||
    [9, 2270.8142, 1132.5826, 107.32704, 2270.8142],
 | 
			
		||||
    [10, 2278.7473, 1240.2297, 101.85461, 101.85461],
 | 
			
		||||
    [10, 667.04791, 1016.549, 99.857468, 1390.016],
 | 
			
		||||
    [10, 1583.7395, 1014.5519, 798.85974, 1390.016],
 | 
			
		||||
    [10, 2280.7446, 944.65161, 99.857468, 2456.4937],
 | 
			
		||||
    [11, 830.81415, 1194.2953, 97.860321, 101.85461],
 | 
			
		||||
    [11, 774.89398, 1198.2897, 960.62885, 99.857468],
 | 
			
		||||
    [11, 609.13055, 1192.2982, 1769.4744, 101.85461],
 | 
			
		||||
    [11, 363.4812, 868.75995, 99.857468, 1348.0758],
 | 
			
		||||
    [11, 1495.8649, 870.75714, 493.2959, 1346.0787],
 | 
			
		||||
    [11, 357.48975, 870.75714, 2023.1123, 1348.0758],
 | 
			
		||||
    [11, 529.24457, 499.28735, 103.85177, 2280.7446],
 | 
			
		||||
    [11, 531.24176, 687.01941, 99.857468, 2716.123],
 | 
			
		||||
    [11, 1022.5405, 1120.4008, 669.04504, 2280.7446],
 | 
			
		||||
    [11, 657.06213, 1120.4008, 1725.5371, 2280.7446],
 | 
			
		||||
    [12, 704.99371, 341.51254, 619.11633, 281.59805],
 | 
			
		||||
    [12, 393.43842, 551.21326, 1459.9161, 159.77196],
 | 
			
		||||
    [12, 2280.7446, 691.01367, 99.857468, 101.85461],
 | 
			
		||||
    [12, 718.97375, 1198.2897, 99.857468, 840.79987],
 | 
			
		||||
    [12, 712.9823, 1196.2925, 850.78564, 842.79706],
 | 
			
		||||
    [12, 780.88538, 1192.2982, 1599.7167, 842.79706],
 | 
			
		||||
    [12, 2366.6221, 1445.9362, 61.911629, 2049.0752],
 | 
			
		||||
    [12, 922.68298, 551.21326, 984.59467, 2378.605],
 | 
			
		||||
    [12, 631.09918, 211.69783, 1851.3574, 3289.3049],
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -11,59 +11,66 @@
 | 
			
		|||
 | 
			
		||||
    <body>
 | 
			
		||||
    
 | 
			
		||||
        <!-- Melpomene comic reader -->
 | 
			
		||||
        <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
        <div id="reader-frame">
 | 
			
		||||
            <div id="reader-content-frame">
 | 
			
		||||
                <div id="reader-pages" class="animated" data-pages-width="2481" data-pages-height="3503" hidden>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg"/>
 | 
			
		||||
        <div id="melpomene">
 | 
			
		||||
            <!-- Melpomene comic reader -->
 | 
			
		||||
            <!-- Version 1.0.0_RC1 -->
 | 
			
		||||
            <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
            
 | 
			
		||||
            <div id="melpomene-content-frame">
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-pages" hidden>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.44702,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg" data-zooms="1459.9161,960.62878,99.857468,103.85177;788.87384,960.62878,1593.7252,103.85177;455.35007,305.56384,389.44412,1376.0359;2282.7415,760.914,99.857475,1114.4093;1069.9728,496.29166,101.85461,1928.2478;1209.7733,496.29166,1172.3267,1928.2478;788.87402,926.67731,101.85462,2474.468;415.40707,926.67731,924.68018,2474.468;1008.5604,926.67731,1372.0416,2474.468"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg" data-zooms="2278.4634,1424.0264,103.9937,103.9937;457.3472,589.15906,816.83411,469.33011;618.69055,832.04285,104.47829,1578.7172;1170.7311,832.04285,756.71887,1578.7172;418.14838,832.04285,1962.6545,1578.7172;2280.6614,940.57422,100.82664,2461.1147"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg" data-zooms="1128.3893,547.21899,100.85604,101.85461;1125.3937,505.27878,101.85462,700.99945;659.59674,1104.1381,1237.9126,100.46677;474.09705,1104.7645,1908.0801,103.04887;991.58466,641.08496,365.47833,1256.2069;2276.7502,1268.1899,101.85461,1256.2069;669.66565,827.24689,100.95136,2574.8799;1574.1992,825.24969,806.56573,2576.877"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg" data-zooms="2270.7588,1348.0758,107.84606,109.84322;828.80096,1879.4762,104.70337,1519.7256;606.50098,1877.479,975.78717,1521.7228;755.02411,1139.5406,1623.401,1521.7228;757.02124,680.4198,1621.4038,2716.7849"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg" data-zooms="2273.6387,1248.3829,104.50265,107.32705;624.19147,440.60574,1259.6805,771.06006;830.37231,940.5238,110.15143,1415.0222;1395.2515,1313.3441,985.71411,1409.3734;1321.8173,796.47961,112.97583,2417.6829;974.4165,754.11365,1403.7247,2646.4587"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg" data-zooms="828.81702,497.29019,443.36716,531.2417;2275.0127,1188.0789,105.30553,109.78442;2275.0127,804.28339,105.30553,1356.3481;847.87823,1182.9146,105.30553,2210.6436;497.6918,1185.7389,994.37964,2213.468;854.35431,1185.7389,1528.7885,2213.468"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg" data-zooms="2272.7561,1228.2468,101.85461,107.84607;497.29019,756.91962,994.58044,569.18756;2276.7502,1052.4977,101.85461,1336.0929;2278.7473,958.63165,101.85462,2444.5107"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg" data-zooms="1131.1705,453.31552,101.67825,103.09045;1148.1168,453.31552,1232.8488,103.09045;1481.6407,453.31552,501.89209,103.09045;2282.1118,855.79193,98.853851,604.42072;1776.5449,881.21149,290.91275,1355.71;703.27454,398.23981,762.58685,2863.9373;2270.8142,1132.5826,107.32704,2270.8142"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg" data-zooms="2278.7473,1240.2297,101.85461,101.85461;667.04791,1016.549,99.857468,1390.016;1583.7395,1014.5519,798.85974,1390.016;2280.7446,944.65161,99.857468,2456.4937"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg" data-zooms="830.81415,1194.2953,97.860321,101.85461;774.89398,1198.2897,960.62885,99.857468;609.13055,1192.2982,1769.4744,101.85461;363.4812,868.75995,99.857468,1348.0758;1495.8649,870.75714,493.2959,1346.0787;357.48975,870.75714,2023.1123,1348.0758;529.24457,499.28735,103.85177,2280.7446;531.24176,687.01941,99.857468,2716.123;1022.5405,1120.4008,669.04504,2280.7446;657.06213,1120.4008,1725.5371,2280.7446"/>
 | 
			
		||||
                    <img loading="lazy" height="3503" width="2481" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg" data-zooms="704.99371,341.51254,619.11633,281.59805;393.43842,551.21326,1459.9161,159.77196;2280.7446,691.01367,99.857468,101.85461;718.97375,1198.2897,99.857468,840.79987;712.9823,1196.2925,850.78564,842.79706;780.88538,1192.2982,1599.7167,842.79706;2366.6221,1445.9362,61.911629,2049.0752;922.68298,551.21326,984.59467,2378.605;631.09918,211.69783,1851.3574,3289.3049"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div id="focus-overlay" class="flex-col fill">
 | 
			
		||||
                    <div class="grow obscured animated"></div>
 | 
			
		||||
                    <div id="focus-overlay-height" class="flex animated" style="height:100%">
 | 
			
		||||
                        <div class="grow obscured animated"></div>
 | 
			
		||||
                        <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
 | 
			
		||||
                        <div class="grow obscured animated"></div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-focus-container">
 | 
			
		||||
                    <div></div>
 | 
			
		||||
                    <div id="melpomene-focus-col">
 | 
			
		||||
                        <div></div>
 | 
			
		||||
                        <div id="melpomene-focus"></div>
 | 
			
		||||
                        <div></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="grow obscured animated"></div>
 | 
			
		||||
                    <div></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div id="nav-controls" class="fill">
 | 
			
		||||
                    <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
 | 
			
		||||
                    <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-help-menu" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                    <div><div class="melpomene-key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                    <div><div class="melpomene-key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                    <div>-----------------------</div>
 | 
			
		||||
                    <div><div class="melpomene-key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                    <div><div class="melpomene-key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                    <div><div class="melpomene-key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
                    <div>-----------------------</div>
 | 
			
		||||
                    <div id="melpomene-version" class="melpomene-credits">Melpomene comic reader - Unknown version</div>
 | 
			
		||||
                    <div class="melpomene-credits">CC-BY-NC-SA 4.0 / <a target="_blank" href="https://git.aribaud.net/caribaud/melpomene">credits</a></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div id="help-menu">
 | 
			
		||||
                    <div id="help-controls" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                        <div><div class="key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                        <div><div class="key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                        <div>-----------------------</div>
 | 
			
		||||
                        <div><div class="key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                        <div><div class="key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                        <div><div class="key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-nav-controls">
 | 
			
		||||
                    <div onclick="moveReader(false,false)"></div>
 | 
			
		||||
                    <div onclick="moveReader(true,false)"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="reader-progress-container">
 | 
			
		||||
                <div id="reader-progress-bar"></div>
 | 
			
		||||
                <div id="reader-progress-pages"></div>
 | 
			
		||||
            
 | 
			
		||||
            <div id="melpomene-progress-container">
 | 
			
		||||
                <div id="melpomene-progress-bar"></div>
 | 
			
		||||
                <div id="melpomene-progress-sections"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- End of Melpomene comic reader -->
 | 
			
		||||
 | 
			
		||||
    </body>
 | 
			
		||||
    
 | 
			
		||||
    <!-- melpomene_js.html import -->
 | 
			
		||||
    <script src="demo_data.js"></script> 
 | 
			
		||||
    <script src="../melpomene.js"></script> 
 | 
			
		||||
    
 | 
			
		||||
</html>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,69 +0,0 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
 | 
			
		||||
    <head>
 | 
			
		||||
        <link rel="stylesheet" href="demo.css">
 | 
			
		||||
        
 | 
			
		||||
        <!-- melpomene_head.html import -->
 | 
			
		||||
        <link rel="stylesheet" href="../melpomene.css">
 | 
			
		||||
    
 | 
			
		||||
    </head>
 | 
			
		||||
 | 
			
		||||
    <body>
 | 
			
		||||
    
 | 
			
		||||
        <!-- Melpomene comic reader -->
 | 
			
		||||
        <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
        <div id="reader-frame">
 | 
			
		||||
            <div id="reader-content-frame">
 | 
			
		||||
                <div id="reader-pages" class="animated" data-pages-width="2481" data-pages-height="3503" hidden>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.44702,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg" data-zooms="1459.9161,960.62878,99.857468,103.85177;788.87384,960.62878,1593.7252,103.85177;455.35007,305.56384,389.44412,1376.0359;2282.7415,760.914,99.857475,1114.4093;1069.9728,496.29166,101.85461,1928.2478;1209.7733,496.29166,1172.3267,1928.2478;788.87402,926.67731,101.85462,2474.468;415.40707,926.67731,924.68018,2474.468;1008.5604,926.67731,1372.0416,2474.468"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg" data-zooms="2278.4634,1424.0264,103.9937,103.9937;457.3472,589.15906,816.83411,469.33011;618.69055,832.04285,104.47829,1578.7172;1170.7311,832.04285,756.71887,1578.7172;418.14838,832.04285,1962.6545,1578.7172;2280.6614,940.57422,100.82664,2461.1147"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg" data-zooms="1128.3893,547.21899,100.85604,101.85461;1125.3937,505.27878,101.85462,700.99945;659.59674,1104.1381,1237.9126,100.46677;474.09705,1104.7645,1908.0801,103.04887;991.58466,641.08496,365.47833,1256.2069;2276.7502,1268.1899,101.85461,1256.2069;669.66565,827.24689,100.95136,2574.8799;1574.1992,825.24969,806.56573,2576.877"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg" data-zooms="2270.7588,1348.0758,107.84606,109.84322;828.80096,1879.4762,104.70337,1519.7256;606.50098,1877.479,975.78717,1521.7228;755.02411,1139.5406,1623.401,1521.7228;757.02124,680.4198,1621.4038,2716.7849"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg" data-zooms="2273.6387,1248.3829,104.50265,107.32705;624.19147,440.60574,1259.6805,771.06006;830.37231,940.5238,110.15143,1415.0222;1395.2515,1313.3441,985.71411,1409.3734;1321.8173,796.47961,112.97583,2417.6829;974.4165,754.11365,1403.7247,2646.4587"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg" data-zooms="828.81702,497.29019,443.36716,531.2417;2275.0127,1188.0789,105.30553,109.78442;2275.0127,804.28339,105.30553,1356.3481;847.87823,1182.9146,105.30553,2210.6436;497.6918,1185.7389,994.37964,2213.468;854.35431,1185.7389,1528.7885,2213.468"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg" data-zooms="2272.7561,1228.2468,101.85461,107.84607;497.29019,756.91962,994.58044,569.18756;2276.7502,1052.4977,101.85461,1336.0929;2278.7473,958.63165,101.85462,2444.5107"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg" data-zooms="1131.1705,453.31552,101.67825,103.09045;1148.1168,453.31552,1232.8488,103.09045;1481.6407,453.31552,501.89209,103.09045;2282.1118,855.79193,98.853851,604.42072;1776.5449,881.21149,290.91275,1355.71;703.27454,398.23981,762.58685,2863.9373;2270.8142,1132.5826,107.32704,2270.8142"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg" data-zooms="2278.7473,1240.2297,101.85461,101.85461;667.04791,1016.549,99.857468,1390.016;1583.7395,1014.5519,798.85974,1390.016;2280.7446,944.65161,99.857468,2456.4937"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg" data-zooms="830.81415,1194.2953,97.860321,101.85461;774.89398,1198.2897,960.62885,99.857468;609.13055,1192.2982,1769.4744,101.85461;363.4812,868.75995,99.857468,1348.0758;1495.8649,870.75714,493.2959,1346.0787;357.48975,870.75714,2023.1123,1348.0758;529.24457,499.28735,103.85177,2280.7446;531.24176,687.01941,99.857468,2716.123;1022.5405,1120.4008,669.04504,2280.7446;657.06213,1120.4008,1725.5371,2280.7446"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/hi-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg" data-zooms="704.99371,341.51254,619.11633,281.59805;393.43842,551.21326,1459.9161,159.77196;2280.7446,691.01367,99.857468,101.85461;718.97375,1198.2897,99.857468,840.79987;712.9823,1196.2925,850.78564,842.79706;780.88538,1192.2982,1599.7167,842.79706;2366.6221,1445.9362,61.911629,2049.0752;922.68298,551.21326,984.59467,2378.605;631.09918,211.69783,1851.3574,3289.3049"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div id="focus-overlay" class="flex-col fill">
 | 
			
		||||
                    <div class="grow obscured animated"></div>
 | 
			
		||||
                    <div id="focus-overlay-height" class="flex animated" style="height:100%">
 | 
			
		||||
                        <div class="grow obscured animated"></div>
 | 
			
		||||
                        <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
 | 
			
		||||
                        <div class="grow obscured animated"></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="grow obscured animated"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div id="nav-controls" class="fill">
 | 
			
		||||
                    <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
 | 
			
		||||
                    <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div id="help-menu">
 | 
			
		||||
                    <div id="help-controls" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                        <div><div class="key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                        <div><div class="key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                        <div>-----------------------</div>
 | 
			
		||||
                        <div><div class="key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                        <div><div class="key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                        <div><div class="key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="reader-progress-container">
 | 
			
		||||
                <div id="reader-progress-bar"></div>
 | 
			
		||||
                <div id="reader-progress-pages"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- End of Melpomene comic reader -->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    </body>
 | 
			
		||||
    
 | 
			
		||||
    <!-- melpomene_js.html import -->
 | 
			
		||||
    <script src="../melpomene.js"></script> 
 | 
			
		||||
    
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -11,67 +11,66 @@
 | 
			
		|||
 | 
			
		||||
    <body>
 | 
			
		||||
    
 | 
			
		||||
        <!-- melpomene_reader.html import -->
 | 
			
		||||
    
 | 
			
		||||
        <!-- Melpomene comic reader -->
 | 
			
		||||
        <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
        <div id="reader-frame">
 | 
			
		||||
            <div id="reader-content-frame">
 | 
			
		||||
                
 | 
			
		||||
                <div id="reader-pages" class="animated" data-pages-width="2481" data-pages-height="3503" hidden>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg"/>
 | 
			
		||||
                    <img loading="lazy" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="focus-overlay" class="flex-col fill">
 | 
			
		||||
                    <div class="grow obscured animated"></div>
 | 
			
		||||
                    <div id="focus-overlay-height" class="flex animated" style="height:100%">
 | 
			
		||||
                        <div class="grow obscured animated"></div>
 | 
			
		||||
                        <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
 | 
			
		||||
                        <div class="grow obscured animated"></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="grow obscured animated"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="nav-controls" class="fill">
 | 
			
		||||
                    <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
 | 
			
		||||
                    <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="help-menu">
 | 
			
		||||
                    <div id="help-controls" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                        <div><div class="key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                        <div><div class="key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                        <div>-----------------------</div>
 | 
			
		||||
                        <div><div class="key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                        <div><div class="key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                        <div><div class="key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
        <div id="melpomene">
 | 
			
		||||
            <!-- Melpomene comic reader -->
 | 
			
		||||
            <!-- Version 1.0.0_RC1 -->
 | 
			
		||||
            <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
            
 | 
			
		||||
            <div id="melpomene-content-frame">
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-pages" data-global-zoom-scale="0.4836759371221282" data-global-zoom-offset="0,-70" hidden>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P01.jpg" data-zooms="2481.0,1327.1057,0.0,0.0;593.15338,1076.4635,0.0,1364.053;890.72864,491.29874,830.81415,1751.5;2481.0,1078.4192,0.0,1364.053;562.77032,909.44702,102.48115,2491.6567;920.74463,909.44702,698.55927,2491.6567;728.776,909.44702,1652.3695,2491.6567"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P02.jpg" data-zooms="1459.9161,960.62878,99.857468,103.85177;788.87384,960.62878,1593.7252,103.85177;455.35007,305.56384,389.44412,1376.0359;2282.7415,760.914,99.857475,1114.4093;1069.9728,496.29166,101.85461,1928.2478;1209.7733,496.29166,1172.3267,1928.2478;788.87402,926.67731,101.85462,2474.468;415.40707,926.67731,924.68018,2474.468;1008.5604,926.67731,1372.0416,2474.468"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P03.jpg" data-zooms="2278.4634,1424.0264,103.9937,103.9937;457.3472,589.15906,816.83411,469.33011;618.69055,832.04285,104.47829,1578.7172;1170.7311,832.04285,756.71887,1578.7172;418.14838,832.04285,1962.6545,1578.7172;2280.6614,940.57422,100.82664,2461.1147"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P04.jpg" data-zooms="1128.3893,547.21899,100.85604,101.85461;1125.3937,505.27878,101.85462,700.99945;659.59674,1104.1381,1237.9126,100.46677;474.09705,1104.7645,1908.0801,103.04887;991.58466,641.08496,365.47833,1256.2069;2276.7502,1268.1899,101.85461,1256.2069;669.66565,827.24689,100.95136,2574.8799;1574.1992,825.24969,806.56573,2576.877"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P05.jpg" data-zooms="2270.7588,1348.0758,107.84606,109.84322;828.80096,1879.4762,104.70337,1519.7256;606.50098,1877.479,975.78717,1521.7228;755.02411,1139.5406,1623.401,1521.7228;757.02124,680.4198,1621.4038,2716.7849"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P06.jpg" data-zooms="2273.6387,1248.3829,104.50265,107.32705;624.19147,440.60574,1259.6805,771.06006;830.37231,940.5238,110.15143,1415.0222;1395.2515,1313.3441,985.71411,1409.3734;1321.8173,796.47961,112.97583,2417.6829;974.4165,754.11365,1403.7247,2646.4587"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P07.jpg" data-zooms="828.81702,497.29019,443.36716,531.2417;2275.0127,1188.0789,105.30553,109.78442;2275.0127,804.28339,105.30553,1356.3481;847.87823,1182.9146,105.30553,2210.6436;497.6918,1185.7389,994.37964,2213.468;854.35431,1185.7389,1528.7885,2213.468"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P08.jpg" data-zooms="2272.7561,1228.2468,101.85461,107.84607;497.29019,756.91962,994.58044,569.18756;2276.7502,1052.4977,101.85461,1336.0929;2278.7473,958.63165,101.85462,2444.5107"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P09.jpg" data-zooms="1131.1705,453.31552,101.67825,103.09045;1148.1168,453.31552,1232.8488,103.09045;1481.6407,453.31552,501.89209,103.09045;2282.1118,855.79193,98.853851,604.42072;1776.5449,881.21149,290.91275,1355.71;703.27454,398.23981,762.58685,2863.9373;2270.8142,1132.5826,107.32704,2270.8142"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P10.jpg" data-zooms="2278.7473,1240.2297,101.85461,101.85461;667.04791,1016.549,99.857468,1390.016;1583.7395,1014.5519,798.85974,1390.016;2280.7446,944.65161,99.857468,2456.4937"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P11.jpg" data-zooms="830.81415,1194.2953,97.860321,101.85461;774.89398,1198.2897,960.62885,99.857468;609.13055,1192.2982,1769.4744,101.85461;363.4812,868.75995,99.857468,1348.0758;1495.8649,870.75714,493.2959,1346.0787;357.48975,870.75714,2023.1123,1348.0758;529.24457,499.28735,103.85177,2280.7446;531.24176,687.01941,99.857468,2716.123;1022.5405,1120.4008,669.04504,2280.7446;657.06213,1120.4008,1725.5371,2280.7446"/>
 | 
			
		||||
                    <img loading="lazy" height="1660" width="1200" src="https://www.peppercarrot.com/0_sources/ep35_The-Reflection/low-res/en_Pepper-and-Carrot_by-David-Revoy_E35P12.jpg" data-zooms="704.99371,341.51254,619.11633,281.59805;393.43842,551.21326,1459.9161,159.77196;2280.7446,691.01367,99.857468,101.85461;718.97375,1198.2897,99.857468,840.79987;712.9823,1196.2925,850.78564,842.79706;780.88538,1192.2982,1599.7167,842.79706;2366.6221,1445.9362,61.911629,2049.0752;922.68298,551.21326,984.59467,2378.605;631.09918,211.69783,1851.3574,3289.3049"/>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-focus-container">
 | 
			
		||||
                    <div></div>
 | 
			
		||||
                    <div id="melpomene-focus-col">
 | 
			
		||||
                        <div></div>
 | 
			
		||||
                        <div id="melpomene-focus"></div>
 | 
			
		||||
                        <div></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-help-menu" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                    <div><div class="melpomene-key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                    <div><div class="melpomene-key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                    <div>-----------------------</div>
 | 
			
		||||
                    <div><div class="melpomene-key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                    <div><div class="melpomene-key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                    <div><div class="melpomene-key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
                    <div>-----------------------</div>
 | 
			
		||||
                    <div id="melpomene-version" class="melpomene-credits">Melpomene comic reader - Unknown version</div>
 | 
			
		||||
                    <div class="melpomene-credits">CC-BY-NC-SA 4.0 / <a target="_blank" href="https://git.aribaud.net/caribaud/melpomene">credits</a></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
                <div id="melpomene-nav-controls">
 | 
			
		||||
                    <div onclick="moveReader(false,false)"></div>
 | 
			
		||||
                    <div onclick="moveReader(true,false)"></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
            <div id="reader-progress-container">
 | 
			
		||||
                <div id="reader-progress-bar"></div>
 | 
			
		||||
                <div id="reader-progress-pages"></div>
 | 
			
		||||
            <div id="melpomene-progress-container">
 | 
			
		||||
                <div id="melpomene-progress-bar"></div>
 | 
			
		||||
                <div id="melpomene-progress-sections"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- End of Melpomene comic reader -->
 | 
			
		||||
 | 
			
		||||
    </body>
 | 
			
		||||
    
 | 
			
		||||
    <!-- melpomene_js.html import -->
 | 
			
		||||
    <script src="demo_data.js"></script> 
 | 
			
		||||
    <script src="../melpomene.js"></script> 
 | 
			
		||||
    
 | 
			
		||||
</html>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								eslint/Dockerfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								eslint/Dockerfile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
FROM debian:latest
 | 
			
		||||
 | 
			
		||||
RUN apt-get update && apt-get install -y --no-install-recommends npm
 | 
			
		||||
 | 
			
		||||
RUN npm install eslint --global
 | 
			
		||||
 | 
			
		||||
COPY eslintrc.json /eslintrc.json
 | 
			
		||||
 | 
			
		||||
WORKDIR melpomene
 | 
			
		||||
 | 
			
		||||
CMD ["eslint", "--no-eslintrc", "-c", "eslint/eslintrc.json", "-f", "html", "-o", "eslint_report.html",  "melpomene.js"]
 | 
			
		||||
							
								
								
									
										40
									
								
								eslint/eslintrc.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								eslint/eslintrc.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
{
 | 
			
		||||
    "env": {
 | 
			
		||||
        "browser": true,
 | 
			
		||||
	"es2021": true
 | 
			
		||||
    },
 | 
			
		||||
    "extends": [
 | 
			
		||||
        "eslint:all"
 | 
			
		||||
    ],
 | 
			
		||||
    "rules": {
 | 
			
		||||
        "semi": "warn",
 | 
			
		||||
        "spaced-comment": "warn",
 | 
			
		||||
        "padded-blocks": ["warn", "never"],
 | 
			
		||||
        "camelcase": "warn",
 | 
			
		||||
        "multiline-comment-style": "off",
 | 
			
		||||
        "max-len": "warn",
 | 
			
		||||
        "prefer-template": "warn",
 | 
			
		||||
        "object-curly-spacing" : ["warn", "always"],
 | 
			
		||||
        "func-style": ["warn", "declaration"],
 | 
			
		||||
        "key-spacing": "warn",
 | 
			
		||||
        "one-var": ["warn", "never"],
 | 
			
		||||
        "quotes": "warn",
 | 
			
		||||
        "space-before-function-paren": ["warn", "never"],
 | 
			
		||||
        "no-undefined": "off",
 | 
			
		||||
        "strict": ["error", "global"],
 | 
			
		||||
        "keyword-spacing": "warn",
 | 
			
		||||
        "function-call-argument-newline": "warn",
 | 
			
		||||
        "operator-assignment": "off",
 | 
			
		||||
        "space-before-blocks": "warn",
 | 
			
		||||
        "brace-style": ["warn", "allman", {"allowSingleLine": true}],
 | 
			
		||||
        "function-call-argument-newline" : ["warn", "consistent"],
 | 
			
		||||
        "function-paren-newline": ["warn", "consistent"],
 | 
			
		||||
        "no-magic-numbers": ["error", {"ignore": [0,1,2,100]}],
 | 
			
		||||
        "quote-props": ["warn", "as-needed"],
 | 
			
		||||
        "no-unused-vars": ["error", {"argsIgnorePattern": "^_"}],
 | 
			
		||||
        "max-len" : ["warn", {"code": 88}],
 | 
			
		||||
        "capitalized-comments": "off",
 | 
			
		||||
        "operator-linebreak": ["warn", "before"],
 | 
			
		||||
        "max-lines": "off"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								header_version_updater.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								header_version_updater.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
import re
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
version_input = input("New version = ").strip()
 | 
			
		||||
 | 
			
		||||
js_file = Path("melpomene.js")
 | 
			
		||||
css_file = Path("melpomene.css")
 | 
			
		||||
JS_CSS_MATCH = r"(?<=/\* Version ).*(?= \*/)"
 | 
			
		||||
JS_VARIABLE_MATCH = r"(?<=const MELPOMENE_VERSION = \").*(?=\";)"
 | 
			
		||||
 | 
			
		||||
html_file = Path("melpomene.html")
 | 
			
		||||
demo_high_res_file = Path("demos/pepper_and_carrot_e35_highres.html")
 | 
			
		||||
demo_low_res_file = Path("demos/pepper_and_carrot_e35_lowres.html")
 | 
			
		||||
HTML_MATCH = r"(?<=<!-- Version ).*(?= -->)"
 | 
			
		||||
 | 
			
		||||
zoom_generator_file = Path("zooms_generator.py")
 | 
			
		||||
ZOOM_GENERATOR_MATCH = r"(?<=# Version ).*(?=$)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def replace_version(version, file_path, matcher):
 | 
			
		||||
    with open(file=file_path, mode="r", encoding="UTF8") as file:
 | 
			
		||||
        file_data = file.read()
 | 
			
		||||
 | 
			
		||||
    file_data = re.sub(matcher, version, file_data, flags=re.MULTILINE)
 | 
			
		||||
 | 
			
		||||
    with open(file=file_path, mode="w", encoding="UTF8") as file:
 | 
			
		||||
        file.write(file_data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if re.match(r"[.a-zA-Z\ \-0-9]{1,30}", version_input):
 | 
			
		||||
    replace_version(version_input, js_file, JS_CSS_MATCH)
 | 
			
		||||
    replace_version(version_input, js_file, JS_VARIABLE_MATCH)
 | 
			
		||||
 | 
			
		||||
    replace_version(version_input, css_file, JS_CSS_MATCH)
 | 
			
		||||
 | 
			
		||||
    replace_version(version_input, html_file, HTML_MATCH)
 | 
			
		||||
    replace_version(version_input, demo_high_res_file, HTML_MATCH)
 | 
			
		||||
    replace_version(version_input, demo_low_res_file, HTML_MATCH)
 | 
			
		||||
 | 
			
		||||
    replace_version(version_input, zoom_generator_file, ZOOM_GENERATOR_MATCH)
 | 
			
		||||
 | 
			
		||||
    print("Done")
 | 
			
		||||
else:
 | 
			
		||||
    print("Input is not valid : only use letters, numbers, spaces, dots and dashes")
 | 
			
		||||
							
								
								
									
										188
									
								
								melpomene.css
									
										
									
									
									
								
							
							
						
						
									
										188
									
								
								melpomene.css
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,43 +1,8 @@
 | 
			
		|||
/* Melpomene webcomic reader CSS */
 | 
			
		||||
/* Version 1.0.0 - UNSTABLE */
 | 
			
		||||
/* Version 1.0.0_RC1 */
 | 
			
		||||
/* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --reader-progressbar-height: 0.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.animated {
 | 
			
		||||
    transition: all 1.5s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-frame {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-content-frame {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-pages {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    left: 0px;
 | 
			
		||||
    right: 0px;
 | 
			
		||||
    transform-origin: top left;
 | 
			
		||||
    transform: scale(1) translate(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-pages > img {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transition: all 1s cubic-bezier(.9,.03,.69,.22); /* ease-in quartic */
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fill {
 | 
			
		||||
#melpomene-focus-container, #melpomene-nav-controls, #melpomene-progress-sections {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -45,82 +10,100 @@
 | 
			
		|||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.obscured {
 | 
			
		||||
    background-color: rgba(0, 0, 0, 0.85);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex {
 | 
			
		||||
#melpomene, #melpomene-focus-container, #melpomene-pages, #melpomene-nav-controls, #melpomene-focus-col {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex-col {
 | 
			
		||||
    display: flex;
 | 
			
		||||
#melpomene-pages, #melpomene-focus-container * {
 | 
			
		||||
    transition: all 1.5s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene, #melpomene-focus-col {
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grow {
 | 
			
		||||
    flex: 1
 | 
			
		||||
#melpomene {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    border: 2px solid;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    background-color: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#nav-controls {
 | 
			
		||||
    display: grid;
 | 
			
		||||
/* Reset images style to avoid external CSS interfering */
 | 
			
		||||
#melpomene img {
 | 
			
		||||
    all: initial;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-content-frame {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-pages {
 | 
			
		||||
    transform-origin: top left;
 | 
			
		||||
    transform: scale(1) translate(0);
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    width: fit-content;
 | 
			
		||||
    height: fit-content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-pages > img {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transition: all 1s cubic-bezier(.9,.03,.69,.22); /* ease-in quartic */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-focus-container * {
 | 
			
		||||
    background-color: rgba(0, 0, 0, 0.85);
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-focus-col, #melpomene-focus {
 | 
			
		||||
    background-color: initial;
 | 
			
		||||
    flex-grow: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-focus {
 | 
			
		||||
    box-shadow: inset 0px 0px 5px 5px rgba(0, 0, 0, 0.85);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-nav-controls {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    
 | 
			
		||||
    grid-template-areas: 
 | 
			
		||||
        "left top right"
 | 
			
		||||
        "left center right"
 | 
			
		||||
        "left bottom right";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#nav-controls {
 | 
			
		||||
    
 | 
			
		||||
    grid-template-columns: 33% 0 1fr;
 | 
			
		||||
    grid-template-rows: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#nav-controls > div {
 | 
			
		||||
#melpomene-nav-controls > div {
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.top {
 | 
			
		||||
    grid-area: top;
 | 
			
		||||
#melpomene-nav-controls > div:first-child {
 | 
			
		||||
    width: 35%;
 | 
			
		||||
    flex-grow: 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.left {
 | 
			
		||||
    grid-area: left;
 | 
			
		||||
}
 | 
			
		||||
#melpomene-help-menu {
 | 
			
		||||
 | 
			
		||||
.right {
 | 
			
		||||
    grid-area: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.top {
 | 
			
		||||
    grid-area: top;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bottom {
 | 
			
		||||
    grid-area: bottom;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.focus {
 | 
			
		||||
    box-shadow: inset 0px 0px 5px 5px rgba(0, 0, 0, 0.85);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#help-menu{
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    
 | 
			
		||||
    font-size: 120%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    
 | 
			
		||||
    color: white;
 | 
			
		||||
    background-color: rgba(0,0,0,0.8)
 | 
			
		||||
    border: 0.1em solid black;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    justify-content: end;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    align-items: end;
 | 
			
		||||
}
 | 
			
		||||
    background-color: rgba(0,0,0,0.8);
 | 
			
		||||
 | 
			
		||||
#help-menu > #help-controls {
 | 
			
		||||
    padding: 0 1em 1em 1em;
 | 
			
		||||
    background-color: rgba(0,0,0,0.5);
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transition: all 1.7s cubic-bezier(.76,.05,.86,.06);
 | 
			
		||||
| 
						 | 
				
			
			@ -130,18 +113,28 @@
 | 
			
		|||
    transform: translate(0, calc(100% - 2em));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#help-menu > #help-controls:hover {
 | 
			
		||||
#melpomene-help-menu:hover {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    transition: all 0.3s linear;
 | 
			
		||||
    transform: translate(0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#help-menu > #help-controls > div {
 | 
			
		||||
#melpomene-help-menu * {
 | 
			
		||||
    margin-top: 1em;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.key {
 | 
			
		||||
#melpomene-help-menu .melpomene-credits {
 | 
			
		||||
    margin-top: 0.5em;
 | 
			
		||||
    font-size: 70%;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#melpomene-help-menu .melpomene-credits a {
 | 
			
		||||
    color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.melpomene-key {
 | 
			
		||||
    display: inline;
 | 
			
		||||
    margin: 0 0.5em;
 | 
			
		||||
    border: 1px white solid;
 | 
			
		||||
| 
						 | 
				
			
			@ -149,30 +142,25 @@
 | 
			
		|||
    border-radius: 0.2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-progress-container {
 | 
			
		||||
#melpomene-progress-container {
 | 
			
		||||
    background-color: dimgray;
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-progress-bar{
 | 
			
		||||
    height: var(--reader-progressbar-height);
 | 
			
		||||
#melpomene-progress-bar{
 | 
			
		||||
    height: 0.3em;
 | 
			
		||||
    background-color: whitesmoke;
 | 
			
		||||
    width: 0%;
 | 
			
		||||
    transition: all 0.5s ease-in-out;
 | 
			
		||||
    width: 0%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-progress-pages {
 | 
			
		||||
#melpomene-progress-sections {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#reader-progress-pages > * {
 | 
			
		||||
#melpomene-progress-sections > * {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    border-right: 0.3em black solid;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,39 +1,45 @@
 | 
			
		|||
<!-- Melpomene comic reader -->
 | 
			
		||||
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
<div id="reader-frame">
 | 
			
		||||
    <div id="reader-content-frame">
 | 
			
		||||
        <!-- replace all 'xxxx' with the actual value -->
 | 
			
		||||
        <div id="reader-pages" class="animated" data-pages-width="xxxx" data-pages-height="xxxx" hidden>
 | 
			
		||||
            <img loading="lazy" src="xxxx" data-zooms="xxxx"/>
 | 
			
		||||
            <!-- duplicate img for each comic page -->
 | 
			
		||||
<div id="melpomene">
 | 
			
		||||
    <!-- Melpomene comic reader -->
 | 
			
		||||
    <!-- Version 1.0.0_RC1 -->
 | 
			
		||||
    <!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
    
 | 
			
		||||
    <div id="melpomene-content-frame">
 | 
			
		||||
        
 | 
			
		||||
        <div id="melpomene-pages" hidden>
 | 
			
		||||
            <!-- your img tags here, see documentation -->
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="focus-overlay" class="flex-col fill">
 | 
			
		||||
            <div class="grow obscured animated"></div>
 | 
			
		||||
            <div id="focus-overlay-height" class="flex animated" style="height:100%">
 | 
			
		||||
                <div class="grow obscured animated"></div>
 | 
			
		||||
                <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
 | 
			
		||||
                <div class="grow obscured animated"></div>
 | 
			
		||||
        
 | 
			
		||||
        <div id="melpomene-focus-container">
 | 
			
		||||
            <div></div>
 | 
			
		||||
            <div id="melpomene-focus-col">
 | 
			
		||||
                <div></div>
 | 
			
		||||
                <div id="melpomene-focus"></div>
 | 
			
		||||
                <div></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="grow obscured animated"></div>
 | 
			
		||||
            <div></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="nav-controls" class="fill">
 | 
			
		||||
            <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
 | 
			
		||||
            <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
 | 
			
		||||
        
 | 
			
		||||
        <div id="melpomene-help-menu" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
            <div><div class="melpomene-key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
            <div><div class="melpomene-key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
            <div>-----------------------</div>
 | 
			
		||||
            <div><div class="melpomene-key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
            <div><div class="melpomene-key">P</div>: Toggle progress bar</div>
 | 
			
		||||
            <div><div class="melpomene-key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
            <div>-----------------------</div>
 | 
			
		||||
            <div id="melpomene-version" class="melpomene-credits">Melpomene comic reader - Unknown version</div>
 | 
			
		||||
            <div class="melpomene-credits">CC-BY-NC-SA 4.0 / <a target="_blank" href="https://git.aribaud.net/caribaud/melpomene">credits</a></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="help-menu">
 | 
			
		||||
            <div id="help-controls" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                <div><div class="key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                <div><div class="key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                <div>-----------------------</div>
 | 
			
		||||
                <div><div class="key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                <div><div class="key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                <div><div class="key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        
 | 
			
		||||
        <div id="melpomene-nav-controls">
 | 
			
		||||
            <div onclick="moveReader(false,false)"></div>
 | 
			
		||||
            <div onclick="moveReader(true,false)"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="reader-progress-container">
 | 
			
		||||
        <div id="reader-progress-bar"></div>
 | 
			
		||||
        <div id="reader-progress-pages"></div>
 | 
			
		||||
    
 | 
			
		||||
    <div id="melpomene-progress-container">
 | 
			
		||||
        <div id="melpomene-progress-bar"></div>
 | 
			
		||||
        <div id="melpomene-progress-sections"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
    
 | 
			
		||||
</div>
 | 
			
		||||
<!-- End of Melpomene comic reader -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										853
									
								
								melpomene.js
									
										
									
									
									
								
							
							
						
						
									
										853
									
								
								melpomene.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,351 +1,505 @@
 | 
			
		|||
/* Melpomene webcomic reader JS */
 | 
			
		||||
/* Version 1.0.0 - UNSTABLE */
 | 
			
		||||
/* Version 1.0.0_RC1 */
 | 
			
		||||
/* CC-BY-NC-SA : https://git.aribaud.net/caribaud/melpomene/ */
 | 
			
		||||
 | 
			
		||||
//============
 | 
			
		||||
//  CONTROLS
 | 
			
		||||
//============
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
MOVE_NEXT = "ArrowRight"
 | 
			
		||||
MOVE_BACK = "ArrowLeft"
 | 
			
		||||
TOGGLE_FULLSCREEN = "F"
 | 
			
		||||
TOGGLE_PROGRESSBAR = "P"
 | 
			
		||||
TOGGLE_VIEW_MODE = "V"
 | 
			
		||||
// ============
 | 
			
		||||
//   CONTROLS
 | 
			
		||||
// ============
 | 
			
		||||
 | 
			
		||||
const MOVE_NEXT = "ArrowRight";
 | 
			
		||||
const MOVE_BACK = "ArrowLeft";
 | 
			
		||||
const TOGGLE_FULLSCREEN = "F";
 | 
			
		||||
const TOGGLE_PROGRESSBAR = "P";
 | 
			
		||||
const TOGGLE_VIEW_MODE = "V";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//========================
 | 
			
		||||
//  NAVIGATION CONSTANTS
 | 
			
		||||
//========================
 | 
			
		||||
// ========================
 | 
			
		||||
//   NAVIGATION CONSTANTS
 | 
			
		||||
// ========================
 | 
			
		||||
 | 
			
		||||
PAGE_TRANSITION_SPEED = "1.5s"
 | 
			
		||||
MOUSEWHELL_MIN_DELAY = 50
 | 
			
		||||
DELAY_BEFORE_HIDDING_CONTROLS = 4000;
 | 
			
		||||
const MOUSEWHELL_MIN_DELAY = 50;
 | 
			
		||||
const DELAY_BEFORE_HIDDING_CONTROLS = 4000;
 | 
			
		||||
 | 
			
		||||
//====================
 | 
			
		||||
//  STATES CONSTANTS
 | 
			
		||||
//====================
 | 
			
		||||
// ====================
 | 
			
		||||
//   STATES CONSTANTS
 | 
			
		||||
// ====================
 | 
			
		||||
 | 
			
		||||
READER_FRAME = document.getElementById("reader-frame")
 | 
			
		||||
READER_CONTENT_FRAME = document.getElementById("reader-content-frame")
 | 
			
		||||
READER_PAGES = document.getElementById("reader-pages")
 | 
			
		||||
FOCUS_OVERLAY_HEIGHT = document.getElementById("focus-overlay-height")
 | 
			
		||||
FOCUS_OVERLAY_WIDTH = document.getElementById("focus-overlay-width")
 | 
			
		||||
HELP_CONTROLS = document.getElementById("help-controls")
 | 
			
		||||
PROGRESS_BAR_CONTAINER = document.getElementById("reader-progress-container")
 | 
			
		||||
PROGRESS_BAR = document.getElementById("reader-progress-bar")
 | 
			
		||||
PROGRESS_BAR_PAGES = document.getElementById("reader-progress-pages")
 | 
			
		||||
const MELPOMENE_VERSION = "1.0.0_UNSTABLE";
 | 
			
		||||
 | 
			
		||||
//===========================
 | 
			
		||||
//  STATES GLOBAL VARIABLES
 | 
			
		||||
//===========================
 | 
			
		||||
const READER_FRAME = document.getElementById("melpomene");
 | 
			
		||||
const READER_CONTENT_FRAME = document.getElementById("melpomene-content-frame");
 | 
			
		||||
const READER_PAGES = document.getElementById("melpomene-pages");
 | 
			
		||||
const FOCUS_OVERLAY_HEIGHT = document.getElementById("melpomene-focus");
 | 
			
		||||
const FOCUS_OVERLAY_WIDTH = document.getElementById("melpomene-focus-col");
 | 
			
		||||
const HELP_CONTROLS = document.getElementById("melpomene-help-menu");
 | 
			
		||||
const PROGRESS_BAR_CONTAINER = document.getElementById("melpomene-progress-container");
 | 
			
		||||
const PROGRESS_BAR = document.getElementById("melpomene-progress-bar");
 | 
			
		||||
const PROGRESS_BAR_PAGES = document.getElementById("melpomene-progress-sections");
 | 
			
		||||
const VERSION_DISPLAY = document.getElementById("melpomene-version");
 | 
			
		||||
 | 
			
		||||
// ====================
 | 
			
		||||
//   INDEX CONSTANTS
 | 
			
		||||
// ====================
 | 
			
		||||
 | 
			
		||||
const ZOOM_PAGE_INDEX = 0;
 | 
			
		||||
const ZOOM_WIDTH_INDEX = 1;
 | 
			
		||||
const ZOOM_HEIGHT_INDEX = 2;
 | 
			
		||||
const ZOOM_X_INDEX = 3;
 | 
			
		||||
const ZOOM_Y_INDEX = 4;
 | 
			
		||||
 | 
			
		||||
// ===========================
 | 
			
		||||
//   STATES GLOBAL VARIABLES
 | 
			
		||||
// ===========================
 | 
			
		||||
 | 
			
		||||
let PAGES_ZOOMS;
 | 
			
		||||
// The variable ZOOMS can either be defined by another JS file or contructed at init
 | 
			
		||||
if (typeof PAGES_ZOOMS == 'undefined') {
 | 
			
		||||
    PAGES_ZOOMS = null
 | 
			
		||||
if (typeof PAGES_ZOOMS === "undefined")
 | 
			
		||||
{
 | 
			
		||||
    PAGES_ZOOMS = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CURRENT_ZOOM = 0
 | 
			
		||||
CURRENT_PAGE = 1
 | 
			
		||||
CURRENT_WIDTH = 0
 | 
			
		||||
CURRENT_HEIGHT = 0
 | 
			
		||||
CURRENT_X = 0
 | 
			
		||||
CURRENT_Y = 0
 | 
			
		||||
let CURRENT_ZOOM = 0;
 | 
			
		||||
let CURRENT_PAGE = 1;
 | 
			
		||||
let CURRENT_WIDTH = 0;
 | 
			
		||||
let CURRENT_HEIGHT = 0;
 | 
			
		||||
let CURRENT_X = 0;
 | 
			
		||||
let CURRENT_Y = 0;
 | 
			
		||||
 | 
			
		||||
IS_PAGE_MODE = false
 | 
			
		||||
MOUSEWHELL_WAIT = false
 | 
			
		||||
let IS_PAGE_MODE = false;
 | 
			
		||||
let MOUSEWHELL_WAIT = false;
 | 
			
		||||
 | 
			
		||||
// =============
 | 
			
		||||
//   UTILITIES
 | 
			
		||||
// =============
 | 
			
		||||
 | 
			
		||||
// Zooms utilites
 | 
			
		||||
// --------------
 | 
			
		||||
 | 
			
		||||
function loadZoomsFromImgTagsIfRequired(){
 | 
			
		||||
    
 | 
			
		||||
    // Zooms may be defined by another JS file
 | 
			
		||||
    if (PAGES_ZOOMS == null){
 | 
			
		||||
        
 | 
			
		||||
        PAGES_ZOOMS = []
 | 
			
		||||
        
 | 
			
		||||
        // parse the data-zooms of each img and and the page number info
 | 
			
		||||
        for (var i = 0; i < READER_PAGES.children.length; i++) {
 | 
			
		||||
            
 | 
			
		||||
            zooms_raw_data = READER_PAGES.children[i].dataset.zooms
 | 
			
		||||
            
 | 
			
		||||
            // ';' separates zooms data, ',' separates values
 | 
			
		||||
            // We add the page number (adding 1 because of indexing)
 | 
			
		||||
            zooms = zooms_raw_data.split(";").map(
 | 
			
		||||
                zoom => [i + 1].concat(
 | 
			
		||||
                    zoom.split(',').map(
 | 
			
		||||
                        value => parseFloat(value)
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
            PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFirstZoomOfPage(pageNumber){
 | 
			
		||||
   
 | 
			
		||||
    for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
 | 
			
		||||
        if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
 | 
			
		||||
            return zoom_idx
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getLastZoomOfPage(pageNumber){
 | 
			
		||||
    let res = null
 | 
			
		||||
    
 | 
			
		||||
    for (var zoom_idx = 0; zoom_idx < PAGES_ZOOMS.length; zoom_idx++){
 | 
			
		||||
        if (PAGES_ZOOMS[zoom_idx][0] == pageNumber) {
 | 
			
		||||
            res = zoom_idx
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (res != null && PAGES_ZOOMS[zoom_idx][0] != pageNumber) {
 | 
			
		||||
            break
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getZoomCountForPage(pageNumber) {
 | 
			
		||||
    return PAGES_ZOOMS.filter(zoom => zoom[0] == pageNumber).length
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCurrentZoomIndexForPage() {
 | 
			
		||||
    previousZoomsCount = PAGES_ZOOMS.filter(zoom => zoom[0] < CURRENT_PAGE).length
 | 
			
		||||
    return CURRENT_ZOOM - previousZoomsCount + 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getReadingProgressPercent() {
 | 
			
		||||
    progressPerPage = 1 / getPagesCount()
 | 
			
		||||
    
 | 
			
		||||
    if (IS_PAGE_MODE){
 | 
			
		||||
        return 100 * progressPerPage * CURRENT_PAGE
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE)
 | 
			
		||||
    
 | 
			
		||||
    readingProgress = (CURRENT_PAGE - 1) * progressPerPage + getCurrentZoomIndexForPage() * progressPerZoom
 | 
			
		||||
    
 | 
			
		||||
    return 100 * readingProgress
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateProgressBar(){
 | 
			
		||||
    PROGRESS_BAR.style.width = getReadingProgressPercent() + "%"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dimensions utilites
 | 
			
		||||
// -------------------
 | 
			
		||||
 | 
			
		||||
function getPagesCount() {
 | 
			
		||||
    return READER_PAGES.childElementCount
 | 
			
		||||
function getPagesCount()
 | 
			
		||||
{
 | 
			
		||||
    return READER_PAGES.childElementCount;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pageOriginalHeight() {
 | 
			
		||||
    return parseInt(READER_PAGES.dataset.pagesHeight)
 | 
			
		||||
function pageOriginalHeight(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    return READER_PAGES.children[pageNumber - 1].naturalHeight;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pageOriginalWidth() {
 | 
			
		||||
    return parseInt(READER_PAGES.dataset.pagesWidth)
 | 
			
		||||
function pageOriginalWidth(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    return READER_PAGES.children[pageNumber - 1].naturalWidth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function readerFrameRatio() {
 | 
			
		||||
    return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight
 | 
			
		||||
function readerFrameRatio()
 | 
			
		||||
{
 | 
			
		||||
    return READER_CONTENT_FRAME.clientWidth / READER_CONTENT_FRAME.clientHeight;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pageRatio() {
 | 
			
		||||
    return READER_PAGES.dataset.pagesWidth / READER_PAGES.dataset.pagesHeight
 | 
			
		||||
}
 | 
			
		||||
function pageMaxHeight()
 | 
			
		||||
{
 | 
			
		||||
    let maxHeight = 0;
 | 
			
		||||
 | 
			
		||||
function isFrameRatioWiderThanPage(){
 | 
			
		||||
    return readerFrameRatio() > pageRatio()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pageToFrameScaleFactor(useHeight){
 | 
			
		||||
    // The scale factor to apply to a page so it exactly fit in the reader frame
 | 
			
		||||
    if (useHeight) {
 | 
			
		||||
        return READER_CONTENT_FRAME.clientHeight / pageOriginalHeight()
 | 
			
		||||
    for (let pageIdx = 0; pageIdx < READER_PAGES.children.length; pageIdx += 1)
 | 
			
		||||
    {
 | 
			
		||||
        if (READER_PAGES.children[pageIdx].naturalHeight > maxHeight)
 | 
			
		||||
        {
 | 
			
		||||
            maxHeight = READER_PAGES.children[pageIdx].naturalHeight;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return READER_CONTENT_FRAME.clientWidth / pageOriginalWidth()
 | 
			
		||||
 | 
			
		||||
    return maxHeight;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function totalPagesWidth() {
 | 
			
		||||
    // The width of all cumuled pages with scale factor applied
 | 
			
		||||
    return pageOriginalWidth() * getPagesCount()
 | 
			
		||||
function pageVerticalOffset(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    return (pageMaxHeight() - pageOriginalHeight(pageNumber)) / 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function previousPagesWidth(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    // The width of all previous pages relative to the provided index
 | 
			
		||||
 | 
			
		||||
    let totalWidth = 0;
 | 
			
		||||
 | 
			
		||||
    for (let idx = 0; idx < pageNumber - 1; idx += 1)
 | 
			
		||||
    {
 | 
			
		||||
        totalWidth += READER_PAGES.children[idx].naturalWidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return totalWidth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Zooms utilites
 | 
			
		||||
// --------------
 | 
			
		||||
 | 
			
		||||
function globalZoomScale()
 | 
			
		||||
{
 | 
			
		||||
    if (READER_PAGES.dataset.globalZoomScale !== undefined)
 | 
			
		||||
    {
 | 
			
		||||
        return parseFloat(READER_PAGES.dataset.globalZoomScale);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 1.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function globalZoomOffsetX()
 | 
			
		||||
{
 | 
			
		||||
    if (READER_PAGES.dataset.globalZoomOffset !== undefined)
 | 
			
		||||
    {
 | 
			
		||||
        return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(",")[0]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function globalZoomOffsetY()
 | 
			
		||||
{
 | 
			
		||||
    if (READER_PAGES.dataset.globalZoomOffset !== undefined)
 | 
			
		||||
    {
 | 
			
		||||
        return parseFloat(READER_PAGES.dataset.globalZoomOffset.split(",")[1]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0.0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadZoomsFromImgTagsIfRequired()
 | 
			
		||||
{
 | 
			
		||||
    // Zooms may be defined by another JS file
 | 
			
		||||
    if (PAGES_ZOOMS === null)
 | 
			
		||||
    {
 | 
			
		||||
        PAGES_ZOOMS = [];
 | 
			
		||||
 | 
			
		||||
        // parse the data-zooms of each img and and the page number info
 | 
			
		||||
        for (let idx = 0; idx < READER_PAGES.children.length; idx += 1)
 | 
			
		||||
        {
 | 
			
		||||
            const zoomsRawData = READER_PAGES.children[idx].dataset.zooms;
 | 
			
		||||
 | 
			
		||||
            // ";" separates zooms data, "," separates values
 | 
			
		||||
            // We add the page number (adding 1 because of indexing)
 | 
			
		||||
            const zooms = zoomsRawData.split(";").map(
 | 
			
		||||
                (zoom) => [idx + 1].concat(
 | 
			
		||||
                    zoom.split(",").map(
 | 
			
		||||
                        (value) => parseFloat(value)
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            PAGES_ZOOMS = PAGES_ZOOMS.concat(zooms);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getFirstZoomOfPage(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    for (let zoomIdx = 0; zoomIdx < PAGES_ZOOMS.length; zoomIdx += 1)
 | 
			
		||||
    {
 | 
			
		||||
        if (PAGES_ZOOMS[zoomIdx][0] === pageNumber)
 | 
			
		||||
        {
 | 
			
		||||
            return zoomIdx;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getZoomCountForPage(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    return PAGES_ZOOMS.filter((zoom) => zoom[0] === pageNumber).length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getCurrentZoomIndexForPage()
 | 
			
		||||
{
 | 
			
		||||
    const previousZoomsCount = PAGES_ZOOMS.filter((zoom) => zoom[0] < CURRENT_PAGE).length;
 | 
			
		||||
    return CURRENT_ZOOM - previousZoomsCount + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getReadingProgressPercent()
 | 
			
		||||
{
 | 
			
		||||
    const progressPerPage = 1 / getPagesCount();
 | 
			
		||||
 | 
			
		||||
    if (IS_PAGE_MODE)
 | 
			
		||||
    {
 | 
			
		||||
        return 100 * progressPerPage * CURRENT_PAGE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const progressPerZoom = progressPerPage / getZoomCountForPage(CURRENT_PAGE);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    let readingProgress = (CURRENT_PAGE - 1) * progressPerPage;
 | 
			
		||||
    readingProgress += getCurrentZoomIndexForPage() * progressPerZoom;
 | 
			
		||||
 | 
			
		||||
    return 100 * readingProgress;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateProgressBar()
 | 
			
		||||
{
 | 
			
		||||
    PROGRESS_BAR.style.width = `${getReadingProgressPercent()}%`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// =========
 | 
			
		||||
//  ACTIONS
 | 
			
		||||
// =========
 | 
			
		||||
 | 
			
		||||
function initReader(){
 | 
			
		||||
    loadZoomsFromImgTagsIfRequired()
 | 
			
		||||
    moveReaderDisplayToZoom(0)
 | 
			
		||||
    
 | 
			
		||||
    // Smoothly show pictures when they intersect with the viewport
 | 
			
		||||
    let visibilityObserver = new IntersectionObserver((entries, observer) => {
 | 
			
		||||
        entries.forEach((entry) => {
 | 
			
		||||
            if (entry.isIntersecting) {
 | 
			
		||||
                entry.target.style.opacity = 1
 | 
			
		||||
                entry.target.style.visibility = "visible"
 | 
			
		||||
            } else {
 | 
			
		||||
                entry.target.style.opacity = 0
 | 
			
		||||
                entry.target.style.visibility = "hidden"
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }, { root: READER_CONTENT_FRAME, rootMargin: "-10px" });
 | 
			
		||||
    
 | 
			
		||||
    for (var i = 0; i < READER_PAGES.children.length; i++) {
 | 
			
		||||
        let img = READER_PAGES.children[i];
 | 
			
		||||
        img.style.width = 100 / getPagesCount() + "%"
 | 
			
		||||
        visibilityObserver.observe(img)
 | 
			
		||||
        
 | 
			
		||||
        PROGRESS_BAR_PAGES.appendChild(document.createElement("div"))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    READER_PAGES.style.display = "flex"
 | 
			
		||||
    
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        READER_PAGES.hidden = false
 | 
			
		||||
    }, "300")
 | 
			
		||||
    
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        HELP_CONTROLS.style.opacity = null;
 | 
			
		||||
        HELP_CONTROLS.style.transform = null;
 | 
			
		||||
    }, DELAY_BEFORE_HIDDING_CONTROLS)
 | 
			
		||||
function updateFocusByWidth(width)
 | 
			
		||||
{
 | 
			
		||||
    FOCUS_OVERLAY_WIDTH.style.width = `${width / READER_CONTENT_FRAME.clientWidth * 100}%`;
 | 
			
		||||
    FOCUS_OVERLAY_HEIGHT.style.height = "100%";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateFocusByHeight(height)
 | 
			
		||||
{
 | 
			
		||||
    FOCUS_OVERLAY_WIDTH.style.width = "100%";
 | 
			
		||||
    FOCUS_OVERLAY_HEIGHT.style.height = `${height / READER_CONTENT_FRAME.clientHeight * 100}%`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function moveReaderDisplayToArea(pageNumber, oWidth, oHeight, oPosx, oPosy)
 | 
			
		||||
{
 | 
			
		||||
    // Keep original values for registering
 | 
			
		||||
    let width = oWidth;
 | 
			
		||||
    let height = oHeight;
 | 
			
		||||
    let posx = oPosx;
 | 
			
		||||
    let posy = oPosy;
 | 
			
		||||
 | 
			
		||||
    // Apply global offsets before scales if we are displaying a zoom
 | 
			
		||||
    // Pages display uses width & height = 0
 | 
			
		||||
    if (width !== 0 || height !== 0)
 | 
			
		||||
    {
 | 
			
		||||
        width = width * globalZoomScale();
 | 
			
		||||
        height = height * globalZoomScale();
 | 
			
		||||
        posx = (posx + globalZoomOffsetX()) * globalZoomScale();
 | 
			
		||||
        posy = (posy + globalZoomOffsetY()) * globalZoomScale();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // reduce width if offset sent us outside of page
 | 
			
		||||
    if (posx < 0)
 | 
			
		||||
    {
 | 
			
		||||
        width = width + posx;
 | 
			
		||||
        posx = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (posx + width > pageOriginalWidth(pageNumber))
 | 
			
		||||
    {
 | 
			
		||||
        width = pageOriginalWidth(pageNumber) - posx;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // reduce height if offset sent us outside of page
 | 
			
		||||
    if (posy < 0)
 | 
			
		||||
    {
 | 
			
		||||
        height = height + posy;
 | 
			
		||||
        posy = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (posy + height > pageOriginalHeight(pageNumber))
 | 
			
		||||
    {
 | 
			
		||||
        height = pageOriginalHeight(pageNumber) - posy;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // Align the top-left corner of the frame with the page
 | 
			
		||||
    READER_PAGES.style.transform = `translate(-${previousPagesWidth(pageNumber)}px, -${pageVerticalOffset(pageNumber)}px)`;
 | 
			
		||||
 | 
			
		||||
function moveReaderDisplayToArea(pageNumber, width, height, posx, posy){
 | 
			
		||||
    
 | 
			
		||||
    // First, scale so the page is at scale 1 compared to the frame
 | 
			
		||||
    READER_PAGES.style.transform = "scale(" + totalPagesWidth() / READER_CONTENT_FRAME.clientWidth + ")"
 | 
			
		||||
    
 | 
			
		||||
    // Then, move to the correct page
 | 
			
		||||
    READER_PAGES.style.transform = "translateX(" + (- pageOriginalWidth() * (pageNumber - 1)) + "px )" + READER_PAGES.style.transform
 | 
			
		||||
    
 | 
			
		||||
    // Then move so the top-left point of the zoom match the frame top-left
 | 
			
		||||
    READER_PAGES.style.transform = "translate(" + (- posx) + "px, " + (-posy) + "px )" + READER_PAGES.style.transform
 | 
			
		||||
    
 | 
			
		||||
    READER_PAGES.style.transform = `translate(${-posx}px, ${-posy}px) ${READER_PAGES.style.transform}`;
 | 
			
		||||
 | 
			
		||||
    // Then, scale so the zoom would fit the frame, and center the zoom
 | 
			
		||||
    if (width == 0){
 | 
			
		||||
        width = pageOriginalWidth()
 | 
			
		||||
    if (width === 0)
 | 
			
		||||
    {
 | 
			
		||||
        width = pageOriginalWidth(pageNumber);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (height == 0){
 | 
			
		||||
        height = pageOriginalHeight()
 | 
			
		||||
 | 
			
		||||
    if (height === 0)
 | 
			
		||||
    {
 | 
			
		||||
        height = pageOriginalHeight(pageNumber);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    zoomRatio = width / height
 | 
			
		||||
    
 | 
			
		||||
    if (readerFrameRatio() > zoomRatio) {
 | 
			
		||||
 | 
			
		||||
    const zoomRatio = width / height;
 | 
			
		||||
 | 
			
		||||
    if (readerFrameRatio() > zoomRatio)
 | 
			
		||||
    {
 | 
			
		||||
        // Frame wider than zoom => scale so heights are the same, offset on x
 | 
			
		||||
        var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height
 | 
			
		||||
        
 | 
			
		||||
        READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
 | 
			
		||||
        
 | 
			
		||||
        var scaledWidth = width * zoomToFrameScaleFactor
 | 
			
		||||
        var offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2
 | 
			
		||||
        
 | 
			
		||||
        READER_PAGES.style.transform = "translateX(" + offset + "px)" + READER_PAGES.style.transform
 | 
			
		||||
        
 | 
			
		||||
        updateFocusByWidth(scaledWidth)
 | 
			
		||||
    } else {
 | 
			
		||||
        const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientHeight / height;
 | 
			
		||||
 | 
			
		||||
        READER_PAGES.style.transform = `scale(${zoomToFrameScaleFactor}) ${READER_PAGES.style.transform}`;
 | 
			
		||||
 | 
			
		||||
        const scaledWidth = width * zoomToFrameScaleFactor;
 | 
			
		||||
        const offset = (READER_CONTENT_FRAME.clientWidth - scaledWidth) / 2;
 | 
			
		||||
 | 
			
		||||
        READER_PAGES.style.transform = `translateX(${offset}px) ${READER_PAGES.style.transform}`;
 | 
			
		||||
 | 
			
		||||
        updateFocusByWidth(scaledWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        // Frame narower than zoom => scale so left/right match, offset on y
 | 
			
		||||
        var zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width 
 | 
			
		||||
        
 | 
			
		||||
        READER_PAGES.style.transform = "scale(" + zoomToFrameScaleFactor + ")" + READER_PAGES.style.transform
 | 
			
		||||
        
 | 
			
		||||
        var scaledHeight = height * zoomToFrameScaleFactor
 | 
			
		||||
        var offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2
 | 
			
		||||
        
 | 
			
		||||
        READER_PAGES.style.transform = "translateY(" + offset + "px)" + READER_PAGES.style.transform
 | 
			
		||||
        
 | 
			
		||||
        updateFocusByHeight(scaledHeight)
 | 
			
		||||
        const zoomToFrameScaleFactor = READER_CONTENT_FRAME.clientWidth / width;
 | 
			
		||||
 | 
			
		||||
        READER_PAGES.style.transform = `scale(${zoomToFrameScaleFactor}) ${READER_PAGES.style.transform}`;
 | 
			
		||||
 | 
			
		||||
        const scaledHeight = height * zoomToFrameScaleFactor;
 | 
			
		||||
        const offset = (READER_CONTENT_FRAME.clientHeight - scaledHeight) / 2;
 | 
			
		||||
 | 
			
		||||
        READER_PAGES.style.transform = `translateY(${offset}px) ${READER_PAGES.style.transform}"`;
 | 
			
		||||
 | 
			
		||||
        updateFocusByHeight(scaledHeight);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    CURRENT_PAGE = pageNumber
 | 
			
		||||
    CURRENT_WIDTH = width
 | 
			
		||||
    CURRENT_HEIGHT = height
 | 
			
		||||
    CURRENT_X = posx
 | 
			
		||||
    CURRENT_Y = posy
 | 
			
		||||
 | 
			
		||||
    // Use values before global offset / scale
 | 
			
		||||
    CURRENT_PAGE = pageNumber;
 | 
			
		||||
    CURRENT_WIDTH = oWidth;
 | 
			
		||||
    CURRENT_HEIGHT = oHeight;
 | 
			
		||||
    CURRENT_X = oPosx;
 | 
			
		||||
    CURRENT_Y = oPosy;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function refreshReaderDisplay() {
 | 
			
		||||
    moveReaderDisplayToArea(CURRENT_PAGE, CURRENT_WIDTH, CURRENT_HEIGHT, CURRENT_X, CURRENT_Y)
 | 
			
		||||
function refreshReaderDisplay()
 | 
			
		||||
{
 | 
			
		||||
    moveReaderDisplayToArea(
 | 
			
		||||
        CURRENT_PAGE,
 | 
			
		||||
        CURRENT_WIDTH,
 | 
			
		||||
        CURRENT_HEIGHT,
 | 
			
		||||
        CURRENT_X,
 | 
			
		||||
        CURRENT_Y
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function moveReaderDisplayToPage(pageNumber) {
 | 
			
		||||
    moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0)
 | 
			
		||||
function moveReaderDisplayToPage(pageNumber)
 | 
			
		||||
{
 | 
			
		||||
    moveReaderDisplayToArea(pageNumber, 0, 0, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function moveReaderDisplayToZoom(index) {
 | 
			
		||||
    
 | 
			
		||||
    moveReaderDisplayToArea(PAGES_ZOOMS[index][0], PAGES_ZOOMS[index][1], PAGES_ZOOMS[index][2], PAGES_ZOOMS[index][3], PAGES_ZOOMS[index][4])
 | 
			
		||||
    
 | 
			
		||||
    CURRENT_ZOOM = index
 | 
			
		||||
function moveReaderDisplayToZoom(zoomIdx)
 | 
			
		||||
{
 | 
			
		||||
    moveReaderDisplayToArea(
 | 
			
		||||
        PAGES_ZOOMS[zoomIdx][ZOOM_PAGE_INDEX],
 | 
			
		||||
        PAGES_ZOOMS[zoomIdx][ZOOM_WIDTH_INDEX],
 | 
			
		||||
        PAGES_ZOOMS[zoomIdx][ZOOM_HEIGHT_INDEX],
 | 
			
		||||
        PAGES_ZOOMS[zoomIdx][ZOOM_X_INDEX],
 | 
			
		||||
        PAGES_ZOOMS[zoomIdx][ZOOM_Y_INDEX]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    CURRENT_ZOOM = zoomIdx;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateFocusByWidth(width){
 | 
			
		||||
    FOCUS_OVERLAY_WIDTH.style.width = (width / READER_CONTENT_FRAME.clientWidth * 100) + "%"
 | 
			
		||||
    FOCUS_OVERLAY_HEIGHT.style.height = "100%"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateFocusByHeight(height){
 | 
			
		||||
    FOCUS_OVERLAY_WIDTH.style.width = "100%"
 | 
			
		||||
    FOCUS_OVERLAY_HEIGHT.style.height = (height / READER_CONTENT_FRAME.clientHeight * 100) + "%"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleViewMode() {
 | 
			
		||||
    if (IS_PAGE_MODE){
 | 
			
		||||
        if (CURRENT_ZOOM != null){
 | 
			
		||||
            moveReaderDisplayToZoom(CURRENT_ZOOM)
 | 
			
		||||
        } else {
 | 
			
		||||
            moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE))
 | 
			
		||||
function toggleViewMode()
 | 
			
		||||
{
 | 
			
		||||
    if (IS_PAGE_MODE)
 | 
			
		||||
    {
 | 
			
		||||
        if (CURRENT_ZOOM === null)
 | 
			
		||||
        {
 | 
			
		||||
            moveReaderDisplayToZoom(getFirstZoomOfPage(CURRENT_PAGE));
 | 
			
		||||
        }
 | 
			
		||||
        IS_PAGE_MODE = false
 | 
			
		||||
    } else {
 | 
			
		||||
        moveReaderDisplayToPage(CURRENT_PAGE)
 | 
			
		||||
        IS_PAGE_MODE = true
 | 
			
		||||
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            moveReaderDisplayToZoom(CURRENT_ZOOM);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IS_PAGE_MODE = false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    updateProgressBar()
 | 
			
		||||
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        moveReaderDisplayToPage(CURRENT_PAGE);
 | 
			
		||||
        IS_PAGE_MODE = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateProgressBar();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function moveReader(to_next) {
 | 
			
		||||
        
 | 
			
		||||
    if (IS_PAGE_MODE){
 | 
			
		||||
        if (to_next && CURRENT_PAGE < getPagesCount()) {
 | 
			
		||||
            moveReaderDisplayToPage(CURRENT_PAGE + 1)
 | 
			
		||||
            CURRENT_ZOOM = null
 | 
			
		||||
function moveReader(toNext)
 | 
			
		||||
{
 | 
			
		||||
    if (IS_PAGE_MODE)
 | 
			
		||||
    {
 | 
			
		||||
        if (toNext && CURRENT_PAGE < getPagesCount())
 | 
			
		||||
        {
 | 
			
		||||
            moveReaderDisplayToPage(CURRENT_PAGE + 1);
 | 
			
		||||
            CURRENT_ZOOM = null;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        else if (!to_next && CURRENT_PAGE > 1) {
 | 
			
		||||
            moveReaderDisplayToPage(CURRENT_PAGE - 1)
 | 
			
		||||
            CURRENT_ZOOM = null
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
    } else {
 | 
			
		||||
    
 | 
			
		||||
        if (to_next && CURRENT_ZOOM < PAGES_ZOOMS.length - 1) {
 | 
			
		||||
            moveReaderDisplayToZoom(CURRENT_ZOOM + 1)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        else if (!to_next && CURRENT_ZOOM > 0) {
 | 
			
		||||
            moveReaderDisplayToZoom(CURRENT_ZOOM - 1)
 | 
			
		||||
 | 
			
		||||
        else if (!toNext && CURRENT_PAGE > 1)
 | 
			
		||||
        {
 | 
			
		||||
            moveReaderDisplayToPage(CURRENT_PAGE - 1);
 | 
			
		||||
            CURRENT_ZOOM = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateProgressBar();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    updateProgressBar()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // Zoom mode
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        if (toNext && CURRENT_ZOOM < PAGES_ZOOMS.length - 1)
 | 
			
		||||
        {
 | 
			
		||||
            moveReaderDisplayToZoom(CURRENT_ZOOM + 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        else if (!toNext && CURRENT_ZOOM > 0)
 | 
			
		||||
        {
 | 
			
		||||
            moveReaderDisplayToZoom(CURRENT_ZOOM - 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateProgressBar();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initReader()
 | 
			
		||||
{
 | 
			
		||||
    VERSION_DISPLAY.innerText = VERSION_DISPLAY.innerText.replace("Unknown version", MELPOMENE_VERSION);
 | 
			
		||||
 | 
			
		||||
    loadZoomsFromImgTagsIfRequired();
 | 
			
		||||
    moveReaderDisplayToZoom(0);
 | 
			
		||||
 | 
			
		||||
    // Smoothly show pictures when they intersect with the viewport
 | 
			
		||||
    const visibilityObserver = new IntersectionObserver(
 | 
			
		||||
        (entries, _observer) =>
 | 
			
		||||
        {
 | 
			
		||||
            entries.forEach((entry) =>
 | 
			
		||||
            {
 | 
			
		||||
                if (entry.isIntersecting)
 | 
			
		||||
                {
 | 
			
		||||
                    entry.target.style.opacity = 1;
 | 
			
		||||
                    entry.target.style.visibility = "visible";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    entry.target.style.opacity = 0;
 | 
			
		||||
                    entry.target.style.visibility = "hidden";
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            root: READER_CONTENT_FRAME,
 | 
			
		||||
            rootMargin: "-10px"
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    for (let pageIndex = 0; pageIndex < READER_PAGES.children.length; pageIndex += 1)
 | 
			
		||||
    {
 | 
			
		||||
        const img = READER_PAGES.children[pageIndex];
 | 
			
		||||
        visibilityObserver.observe(img);
 | 
			
		||||
 | 
			
		||||
        PROGRESS_BAR_PAGES.appendChild(document.createElement("div"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    READER_PAGES.style.display = "flex";
 | 
			
		||||
 | 
			
		||||
    setTimeout(
 | 
			
		||||
        () => { READER_PAGES.hidden = false; },
 | 
			
		||||
        "300"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    setTimeout(
 | 
			
		||||
        () =>
 | 
			
		||||
        {
 | 
			
		||||
            HELP_CONTROLS.style.opacity = null;
 | 
			
		||||
            HELP_CONTROLS.style.transform = null;
 | 
			
		||||
        },
 | 
			
		||||
        DELAY_BEFORE_HIDDING_CONTROLS
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -353,57 +507,86 @@ function moveReader(to_next) {
 | 
			
		|||
//   CALLBACKS
 | 
			
		||||
// =============
 | 
			
		||||
 | 
			
		||||
function handleKeyPress(key){
 | 
			
		||||
    
 | 
			
		||||
    if (key == MOVE_NEXT) {
 | 
			
		||||
        moveReader(true)
 | 
			
		||||
function handleKeyPress(key)
 | 
			
		||||
{
 | 
			
		||||
    if (key === MOVE_NEXT)
 | 
			
		||||
    {
 | 
			
		||||
        moveReader(true);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    else if (key == MOVE_BACK) {
 | 
			
		||||
        moveReader(false)
 | 
			
		||||
 | 
			
		||||
    else if (key === MOVE_BACK)
 | 
			
		||||
    {
 | 
			
		||||
        moveReader(false);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    else if (key.toUpperCase() == TOGGLE_FULLSCREEN){
 | 
			
		||||
        if (document.fullscreenElement == null){
 | 
			
		||||
 | 
			
		||||
    else if (key.toUpperCase() === TOGGLE_FULLSCREEN)
 | 
			
		||||
    {
 | 
			
		||||
        if (document.fullscreenElement === null)
 | 
			
		||||
        {
 | 
			
		||||
            READER_FRAME.requestFullscreen();
 | 
			
		||||
        } else {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            document.exitFullscreen();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    else if (key.toUpperCase() == TOGGLE_PROGRESSBAR){
 | 
			
		||||
        if (PROGRESS_BAR_CONTAINER.hidden == true) {
 | 
			
		||||
            PROGRESS_BAR_CONTAINER.hidden = false
 | 
			
		||||
        } else {
 | 
			
		||||
            PROGRESS_BAR_CONTAINER.hidden = true
 | 
			
		||||
 | 
			
		||||
    else if (key.toUpperCase() === TOGGLE_PROGRESSBAR)
 | 
			
		||||
    {
 | 
			
		||||
        if (PROGRESS_BAR_CONTAINER.hidden === true)
 | 
			
		||||
        {
 | 
			
		||||
            PROGRESS_BAR_CONTAINER.hidden = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            PROGRESS_BAR_CONTAINER.hidden = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        refreshReaderDisplay();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    else if (key.toUpperCase() == TOGGLE_VIEW_MODE) {
 | 
			
		||||
        toggleViewMode()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    else if (key.toUpperCase() === TOGGLE_VIEW_MODE)
 | 
			
		||||
    {
 | 
			
		||||
        toggleViewMode();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleMouseWhell(deltaY){
 | 
			
		||||
    
 | 
			
		||||
    if (MOUSEWHELL_WAIT){
 | 
			
		||||
        return
 | 
			
		||||
    } else {
 | 
			
		||||
        MOUSEWHELL_WAIT = true
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            MOUSEWHELL_WAIT = false
 | 
			
		||||
        }, MOUSEWHELL_MIN_DELAY)
 | 
			
		||||
function handleMouseWhell(event)
 | 
			
		||||
{
 | 
			
		||||
    // Only handle scroll event if the target is the nav controls
 | 
			
		||||
    // to avoid preventing page scrolling.
 | 
			
		||||
 | 
			
		||||
    // Do disable page scrolling when we do prev/next, though
 | 
			
		||||
 | 
			
		||||
    if (!READER_FRAME.contains(event.target))
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (deltaY > 0) {
 | 
			
		||||
        moveReader(true, false)
 | 
			
		||||
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
    if (MOUSEWHELL_WAIT)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    else {
 | 
			
		||||
        moveReader(false, false)
 | 
			
		||||
 | 
			
		||||
    MOUSEWHELL_WAIT = true;
 | 
			
		||||
    setTimeout(
 | 
			
		||||
        () => { MOUSEWHELL_WAIT = false; },
 | 
			
		||||
        MOUSEWHELL_MIN_DELAY
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (event.deltaY > 0)
 | 
			
		||||
    {
 | 
			
		||||
        moveReader(true, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        moveReader(false, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -411,18 +594,26 @@ function handleMouseWhell(deltaY){
 | 
			
		|||
//  INIT
 | 
			
		||||
// ======
 | 
			
		||||
 | 
			
		||||
window.addEventListener("load", (event) => {
 | 
			
		||||
  initReader()
 | 
			
		||||
});
 | 
			
		||||
window.addEventListener(
 | 
			
		||||
    "load",
 | 
			
		||||
    (_event) => { initReader(); }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
addEventListener("resize", (event) => {
 | 
			
		||||
    refreshReaderDisplay();
 | 
			
		||||
});
 | 
			
		||||
addEventListener(
 | 
			
		||||
    "resize",
 | 
			
		||||
    (_event) => { refreshReaderDisplay(); }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
addEventListener("keydown", (event) => {
 | 
			
		||||
    handleKeyPress(event.key, event.shiftKey)
 | 
			
		||||
});
 | 
			
		||||
addEventListener(
 | 
			
		||||
    "keydown",
 | 
			
		||||
    (event) =>
 | 
			
		||||
    {
 | 
			
		||||
        handleKeyPress(event.key, event.shiftKey);
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
addEventListener("wheel", (event) => {
 | 
			
		||||
    handleMouseWhell(event.deltaY)
 | 
			
		||||
});
 | 
			
		||||
addEventListener(
 | 
			
		||||
    "wheel",
 | 
			
		||||
    (event) => { handleMouseWhell(event); },
 | 
			
		||||
    { passive: false }
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,111 +1,98 @@
 | 
			
		|||
# Melpomene webcomic reader JSON/JS/HTML generator
 | 
			
		||||
# Version 1.0.0 - UNSTABLE
 | 
			
		||||
# Version 1.0.0_RC1
 | 
			
		||||
# CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/
 | 
			
		||||
 | 
			
		||||
import sys
 | 
			
		||||
import re
 | 
			
		||||
import xml.etree.ElementTree as ET
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
from xml.etree import ElementTree
 | 
			
		||||
from xml.etree.ElementTree import Element
 | 
			
		||||
from typing import Any
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
HTML_START_CONSTANT = """\
 | 
			
		||||
<!-- Melpomene comic reader -->
 | 
			
		||||
<!-- CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ -->
 | 
			
		||||
<div id="reader-frame">
 | 
			
		||||
    <div id="reader-content-frame">
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
HTML_END_CONSTANT = """\
 | 
			
		||||
        <div id="focus-overlay" class="flex-col fill">
 | 
			
		||||
            <div class="grow obscured animated"></div>
 | 
			
		||||
            <div id="focus-overlay-height" class="flex animated" style="height:100%">
 | 
			
		||||
                <div class="grow obscured animated"></div>
 | 
			
		||||
                <div id="focus-overlay-width" class="focus animated" style="width:100%"></div>
 | 
			
		||||
                <div class="grow obscured animated"></div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="grow obscured animated"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="nav-controls" class="fill">
 | 
			
		||||
            <div class="left" id="nav-left" onclick="moveReader(false,false)"></div>
 | 
			
		||||
            <div class="right" id="nav-right" onclick="moveReader(true,false)"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="help-menu">
 | 
			
		||||
            <div id="help-controls" style="opacity:1; transform: translate(0,0);">
 | 
			
		||||
                <div><div class="key">←</div>/ scroll up / clic : previous</div>
 | 
			
		||||
                <div><div class="key">→</div>/ scroll down / clic  : next</div>
 | 
			
		||||
                <div>-----------------------</div>
 | 
			
		||||
                <div><div class="key">F</div>: Toggle fullscreen</div>
 | 
			
		||||
                <div><div class="key">P</div>: Toggle progress bar</div>
 | 
			
		||||
                <div><div class="key">V</div>: Toggle panel / page viewing mode</div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="reader-progress-container">
 | 
			
		||||
        <div id="reader-progress-bar"></div>
 | 
			
		||||
        <div id="reader-progress-pages"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<!-- End of Melpomene comic reader -->
 | 
			
		||||
"""
 | 
			
		||||
HTML_TEMPLATE = Path(__file__).parent / "melpomene.html"
 | 
			
		||||
HTML_TO_REPLACE = "<!-- your img tags here, see documentation -->"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extract_zooms(src_folder):
 | 
			
		||||
def get_val_has_str(elem: Element, attrib: str, filepath: str | Path) -> str:
 | 
			
		||||
    value = elem.get(attrib)
 | 
			
		||||
 | 
			
		||||
    if value is None:
 | 
			
		||||
        sys.exit(f"Attribute '{attrib}' is not valid in file {filepath}")
 | 
			
		||||
 | 
			
		||||
    return str(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extract_zooms(src_folder) -> dict[int, Any]:
 | 
			
		||||
    folder = Path(src_folder)
 | 
			
		||||
 | 
			
		||||
    zooms = {}
 | 
			
		||||
    
 | 
			
		||||
    max_width = 0
 | 
			
		||||
    max_height = 0
 | 
			
		||||
    
 | 
			
		||||
    pages_zooms: dict[int, Any] = {}
 | 
			
		||||
 | 
			
		||||
    idx = 0
 | 
			
		||||
 | 
			
		||||
    for svg_path in folder.glob("*.svg"):
 | 
			
		||||
        
 | 
			
		||||
        idx += 1
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        print(f"page {idx} : {svg_path.name}")
 | 
			
		||||
    
 | 
			
		||||
        zooms[idx] = {
 | 
			
		||||
 | 
			
		||||
        # Setting up default values
 | 
			
		||||
        pages_zooms[idx] = {
 | 
			
		||||
            "name": svg_path.stem,
 | 
			
		||||
            "width": 0,
 | 
			
		||||
            "height": 0,
 | 
			
		||||
            "zooms": [],
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        tree = ET.parse(svg_path)
 | 
			
		||||
 | 
			
		||||
        tree = ElementTree.parse(svg_path)
 | 
			
		||||
        root = tree.getroot()
 | 
			
		||||
        
 | 
			
		||||
        for svg in root.findall('.//{*}svg'):
 | 
			
		||||
            if area.get("width") > max_width:
 | 
			
		||||
                max_width = area.get("width")
 | 
			
		||||
            if area.get("height") > max_width:
 | 
			
		||||
                max_width = area.get("height")
 | 
			
		||||
        
 | 
			
		||||
        for area in root.findall('.//{*}rect'):
 | 
			
		||||
            zooms[idx]["zooms"].append([
 | 
			
		||||
                float(area.get("width")),
 | 
			
		||||
                float(area.get("height")),
 | 
			
		||||
                float(area.get("x")),
 | 
			
		||||
                float(area.get("y")),
 | 
			
		||||
            ])
 | 
			
		||||
 | 
			
		||||
    return zooms, max_width, max_height
 | 
			
		||||
        width = get_val_has_str(root, "width", svg_path)
 | 
			
		||||
        height = get_val_has_str(root, "height", svg_path)
 | 
			
		||||
 | 
			
		||||
        if "." in width:
 | 
			
		||||
            print(
 | 
			
		||||
                f"WARNING: file {svg_path} has a floating width, it will be rounded",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
        pages_zooms[idx]["width"] = round(float(width))
 | 
			
		||||
 | 
			
		||||
        if "." in height:
 | 
			
		||||
            print(
 | 
			
		||||
                f"WARNING: file {svg_path} has a floating height, it will be rounded",
 | 
			
		||||
                file=sys.stderr,
 | 
			
		||||
            )
 | 
			
		||||
        pages_zooms[idx]["height"] = round(float(height))
 | 
			
		||||
 | 
			
		||||
        zooms = []
 | 
			
		||||
        for area in root.findall(".//{*}rect"):
 | 
			
		||||
            zooms.append(
 | 
			
		||||
                [
 | 
			
		||||
                    float(get_val_has_str(area, "width", svg_path)),
 | 
			
		||||
                    float(get_val_has_str(area, "height", svg_path)),
 | 
			
		||||
                    float(get_val_has_str(area, "x", svg_path)),
 | 
			
		||||
                    float(get_val_has_str(area, "y", svg_path)),
 | 
			
		||||
                ]
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        pages_zooms[idx]["zooms"] = zooms
 | 
			
		||||
 | 
			
		||||
    return pages_zooms
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_json_or_js(zooms, dest_file, is_js):
 | 
			
		||||
 | 
			
		||||
    with open(dest_file, "w") as data_file:
 | 
			
		||||
    
 | 
			
		||||
def write_json_or_js(zooms, dest_file, is_js) -> None:
 | 
			
		||||
    with open(dest_file, "w", encoding="UTF-8") as data_file:
 | 
			
		||||
        if is_js:
 | 
			
		||||
            data_file.write("PAGES_ZOOMS = ")
 | 
			
		||||
        data_file.write("[\n")
 | 
			
		||||
        first_coma_skiped = False
 | 
			
		||||
        for page_idx in sorted(zooms.keys()):
 | 
			
		||||
            for zoom in zooms[page_idx]["zooms"]:
 | 
			
		||||
            
 | 
			
		||||
                if zoom[2] < 0 or zoom[3] < 0 :
 | 
			
		||||
                    print(f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)")
 | 
			
		||||
            
 | 
			
		||||
                if zoom[2] < 0 or zoom[3] < 0:
 | 
			
		||||
                    print(
 | 
			
		||||
                        f"WARNING: negative pos x / pos y in page {page_idx} for "
 | 
			
		||||
                        f"zoom {zoom} (is the rectangle flipped?)"
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                if first_coma_skiped:
 | 
			
		||||
                    data_file.write(",\n")
 | 
			
		||||
                else:
 | 
			
		||||
| 
						 | 
				
			
			@ -114,69 +101,99 @@ def write_json_or_js(zooms, dest_file, is_js):
 | 
			
		|||
        data_file.write("\n]\n")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention):
 | 
			
		||||
    
 | 
			
		||||
    with open(dest_file, "w") as data_file:
 | 
			
		||||
    
 | 
			
		||||
        data_file.write(HTML_START_CONSTANT)
 | 
			
		||||
    
 | 
			
		||||
        data_file.write(f'        <div id="reader-pages" class="animated" data-pages-width="{pages_width}" data-pages-height="{pages_height}" hidden>\n')
 | 
			
		||||
        
 | 
			
		||||
        for page_idx in sorted(zooms.keys()):
 | 
			
		||||
            img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
 | 
			
		||||
            zoom_html_data = [','.join([str(zoom) for zoom in page_zooms]) for page_zooms in zooms[page_idx]["zooms"]]
 | 
			
		||||
            zoom_html_str = ';'.join(zoom_html_data)
 | 
			
		||||
            data_file.write(f'            <img loading="lazy" src="{img_url}" data-zooms="{zoom_html_str}"/>\n')
 | 
			
		||||
        
 | 
			
		||||
        data_file.write(f'        </div>\n')
 | 
			
		||||
        
 | 
			
		||||
        data_file.write(HTML_END_CONSTANT)
 | 
			
		||||
def write_html(zooms, dest_file, prefix, extention) -> None:
 | 
			
		||||
    img_tags = ""
 | 
			
		||||
    for page_idx in sorted(zooms.keys()):
 | 
			
		||||
        img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}"
 | 
			
		||||
        zoom_html_data = [
 | 
			
		||||
            ",".join([str(zoom) for zoom in page_zooms])
 | 
			
		||||
            for page_zooms in zooms[page_idx]["zooms"]
 | 
			
		||||
        ]
 | 
			
		||||
        zoom_html_str = ";".join(zoom_html_data)
 | 
			
		||||
        img_tags = (
 | 
			
		||||
            img_tags
 | 
			
		||||
            + "            "
 | 
			
		||||
            + f'<img loading="lazy" height="{zooms[page_idx]["height"]}" '
 | 
			
		||||
            + f'width="{zooms[page_idx]["width"]}" src="{img_url}" '
 | 
			
		||||
            + f'data-zooms="{zoom_html_str}"/>\n'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
def generate_argparse():
 | 
			
		||||
    """ Generate Melpomene's generator input parser"""
 | 
			
		||||
    
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
        description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader"
 | 
			
		||||
    img_tags = img_tags.strip()
 | 
			
		||||
 | 
			
		||||
    with open(HTML_TEMPLATE, "r", encoding="UTF-8") as template_file, open(
 | 
			
		||||
        dest_file, "w", encoding="UTF-8"
 | 
			
		||||
    ) as data_file:
 | 
			
		||||
        data = template_file.read().replace(HTML_TO_REPLACE, img_tags)
 | 
			
		||||
 | 
			
		||||
        data_file.write(data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_argparse() -> ArgumentParser:
 | 
			
		||||
    """Generate Melpomene's generator input parser"""
 | 
			
		||||
 | 
			
		||||
    parser = ArgumentParser(
 | 
			
		||||
        description=(
 | 
			
		||||
            "Helper that can generate JSON / JS / "
 | 
			
		||||
            "HTML files for Melpomene webcomic reader"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "output_format",
 | 
			
		||||
        choices=["html", "json", "js"],
 | 
			
		||||
        help="The type of output to generate",
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    parser.add_argument("output_format", choices=["html", "json", "js"], help="The type of output to generate")
 | 
			
		||||
    parser.add_argument("svg_folders", help="Path of the folder containing the SVGs")
 | 
			
		||||
    parser.add_argument("-o", metavar="dest_file", help="Where to write the generator output to")
 | 
			
		||||
    parser.add_argument("-p", default="", metavar="img_url_prefix", help="What to prefix the URL of the images when using HTML format.")
 | 
			
		||||
    parser.add_argument("-e", default="png", metavar="img_ext", help="What extention to use in the URL of the images when using HTML format.")
 | 
			
		||||
    
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-o", metavar="dest_file", help="Where to write the generator output to"
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-p",
 | 
			
		||||
        default="",
 | 
			
		||||
        metavar="img_url_prefix",
 | 
			
		||||
        help="What to prefix the URL of the images when using HTML format.",
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        "-e",
 | 
			
		||||
        default="png",
 | 
			
		||||
        metavar="img_ext",
 | 
			
		||||
        help="What extention to use in the URL of the images when using HTML format.",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    return parser
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
 | 
			
		||||
def run():
 | 
			
		||||
    args = generate_argparse().parse_args()
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    # Get the final outout name
 | 
			
		||||
    output = None
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    if not args.o:
 | 
			
		||||
        output = "melpomene_data"
 | 
			
		||||
    else:
 | 
			
		||||
        output = args.o
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    if args.output_format == "html" and not output.endswith(".html"):
 | 
			
		||||
        output += ".html"
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    elif args.output_format == "json" and not output.endswith(".json"):
 | 
			
		||||
        output += ".json"
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    elif args.output_format == "js" and not output.endswith(".js"):
 | 
			
		||||
        output += ".js"
 | 
			
		||||
    
 | 
			
		||||
    zooms, max_width, max_height = extract_zooms(args.svg_folders)
 | 
			
		||||
 | 
			
		||||
    zooms = extract_zooms(args.svg_folders)
 | 
			
		||||
 | 
			
		||||
    if args.output_format == "html":
 | 
			
		||||
        write_html(zooms, output, max_width, max_height, args.p, args.e)
 | 
			
		||||
    
 | 
			
		||||
        write_html(zooms, output, args.p, args.e)
 | 
			
		||||
 | 
			
		||||
    elif args.output_format == "json":
 | 
			
		||||
        write_json_or_js(zooms, output, False)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    elif args.output_format == "js":
 | 
			
		||||
        write_json_or_js(zooms, output, True)
 | 
			
		||||
        write_json_or_js(zooms, output, True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    run()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue