Compare commits
	
		
			33 Commits
		
	
	
		
			583b0fb49f
			...
			translatio
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a886d6575f | |||
| 8124b08b88 | |||
| e80902e965 | |||
| c078eaa6c5 | |||
| 75a6df9d9d | |||
| 9dfc567d5c | |||
| 093d0c0997 | |||
| c86ed21c1e | |||
| 9ce58c9860 | |||
| 355c3055cb | |||
| a5c3cacef9 | |||
| 40db2b814b | |||
| 3c689015ac | |||
| 8a8c24d7ac | |||
| 07f999aeb2 | |||
| 6012d48197 | |||
| 90f08bf454 | |||
| aafa70fc1f | |||
| e3e065af16 | |||
| 9341c58dba | |||
| ecd7027bdd | |||
| 75c814c057 | |||
| 1d41b0cfc7 | |||
| 04dc1e6b90 | |||
| 6b964c81a8 | |||
| fb83a94d4b | |||
| b17f21a04f | |||
| da5dc1da3b | |||
| b9282962f9 | |||
| 4d690e2a2a | |||
| 143dc31c0e | |||
| f2138425b9 | |||
| 3b4cbd8ae1 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | node_modules/ | ||||||
|  | public/bundle.js* | ||||||
|   | |||||||
| @@ -3,7 +3,11 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| steps: | steps: | ||||||
|  |   build: | ||||||
|  |     image: node:latest | ||||||
|  |     commands: | ||||||
|  |       - npm install | ||||||
|  |       - npm run build | ||||||
|   deploy: |   deploy: | ||||||
|     image: appleboy/drone-scp |     image: appleboy/drone-scp | ||||||
|     settings: |     settings: | ||||||
| @@ -14,6 +18,6 @@ steps: | |||||||
|         from_secret: ssh_user |         from_secret: ssh_user | ||||||
|       target: |       target: | ||||||
|         from_secret: path |         from_secret: path | ||||||
|       source: src/ |       source: public/ | ||||||
|       key: |       key: | ||||||
|         from_secret: ssh_key |         from_secret: ssh_key | ||||||
							
								
								
									
										10
									
								
								i18next-parser.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | module.exports = { | ||||||
|  |     defaultNamespace: 'translation', | ||||||
|  |     lexers: { | ||||||
|  |         js: ['JsxLexer'], // we're writing jsx inside .js files | ||||||
|  |         default: ['JavascriptLexer'], | ||||||
|  |     }, | ||||||
|  |     locales: ['en', 'de'], | ||||||
|  |     output: 'public/lang/$LOCALE.json', | ||||||
|  |     input: [ 'src/*.js', 'public/*.html', ], | ||||||
|  | } | ||||||
							
								
								
									
										5269
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										31
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | { | ||||||
|  |   "name": "kaefigrechner", | ||||||
|  |   "version": "0.0.1", | ||||||
|  |   "description": "Ein Rechner für die minimale Größe eines Rattenkäfigs nach Standard des VdRD e.V.", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "start": "webpack-dev-server --config webpack.config.js", | ||||||
|  |     "ex-trans": "i18next -c i18next-parser.config.js", | ||||||
|  |     "build": "webpack --config webpack.config.js" | ||||||
|  |   }, | ||||||
|  |   "repository": { | ||||||
|  |     "type": "git", | ||||||
|  |     "url": "https://git.hyteck.de/moanos/RattenheimRechner" | ||||||
|  |   }, | ||||||
|  |   "keywords": [ | ||||||
|  |     "animal", | ||||||
|  |     "welfare" | ||||||
|  |   ], | ||||||
|  |   "author": "Julian-Samuel Gebühr", | ||||||
|  |   "license": "AGPL-3.0-or-later", | ||||||
|  |   "devDependencies": { | ||||||
|  |     "webpack": "^5.93.0", | ||||||
|  |     "webpack-cli": "^5.1.4", | ||||||
|  |     "webpack-dev-server": "^5.0.4" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "i18next": "^23.12.2", | ||||||
|  |     "i18next-browser-languagedetector": "^8.0.0", | ||||||
|  |     "i18next-http-backend": "^2.5.2" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -20,9 +20,13 @@ | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| body { | body { | ||||||
|  |     margin: 0; | ||||||
|     background: var(--background-one); |     background: var(--background-one); | ||||||
|     color: var(--text-two); |     color: var(--text-two); | ||||||
| } | } | ||||||
|  | .content { | ||||||
|  |     margin: 10px; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| h1, h2 { | h1, h2 { | ||||||
|     word-wrap: break-word; |     word-wrap: break-word; | ||||||
| @@ -30,6 +34,16 @@ h1, h2 { | |||||||
|     text-shadow: 2px 2px var(--shadow-one); |     text-shadow: 2px 2px var(--shadow-one); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | a { | ||||||
|  |     color: inherit; | ||||||
|  |     text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ul { | ||||||
|  |     list-style: none; | ||||||
|  |     padding-left: 0px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .container-form { | .container-form { | ||||||
|     background: var(--background-three); |     background: var(--background-three); | ||||||
|     color: var(--text-two); |     color: var(--text-two); | ||||||
| @@ -47,8 +61,7 @@ h1, h2 { | |||||||
| 
 | 
 | ||||||
| label { | label { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     word-break: break-word;; |     word-break: break-word; | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .slidecontainer { | .slidecontainer { | ||||||
| @@ -77,7 +90,7 @@ label { | |||||||
|     width: 23px; |     width: 23px; | ||||||
|     height: 24px; |     height: 24px; | ||||||
|     border: 0; |     border: 0; | ||||||
|     background: url('/src/assets/img/logo_transparent.png'); |     background: url('../img/logo_transparent.png'); | ||||||
|     background-size: 100% 100%; |     background-size: 100% 100%; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
| } | } | ||||||
| @@ -86,13 +99,12 @@ label { | |||||||
|     width: 23px; |     width: 23px; | ||||||
|     height: 24px; |     height: 24px; | ||||||
|     border: 0; |     border: 0; | ||||||
|     background: url('/src/assets/img/logo_transparent.png'); |     background: url('../img/logo_transparent.png'); | ||||||
|     background-size: 100% 100%; |     background-size: 100% 100%; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| .input-group { | .input-group { | ||||||
|     display: flex; |     display: flex; | ||||||
|     align-items: center; |     align-items: center; | ||||||
| @@ -116,34 +128,48 @@ label { | |||||||
|     height: 36px; |     height: 36px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .cage-selector{ | .cards { | ||||||
|     gap: 15px; |     display: flex; | ||||||
|  |     justify-content: space-between; | ||||||
|  |     flex-wrap: wrap; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| .card { | .card { | ||||||
|     text-align: center; |     box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); | ||||||
|     color: var(--text-two); |     transition: 0.3s; | ||||||
| } |     margin: 0.4em; | ||||||
| .card-photo { |     min-width: 300px; | ||||||
|     height: 200px; |     padding: 10px; | ||||||
|     width: 200px; |     border-radius: 10px; | ||||||
|     background-color: #ffffff; |  | ||||||
|     box-shadow: 0 0 25px rgba(17, 1, 68, 0.08); |  | ||||||
|     border-radius: 8px; |  | ||||||
|     position: relative; |  | ||||||
|     cursor: pointer; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .card-photo img { | @media screen and (min-width: 800px) { | ||||||
|     width: 70%; |     flex: 0 1 calc(25% - 0.5em); | ||||||
|     height: 95%; | } | ||||||
|     position: absolute; | 
 | ||||||
|     margin: auto; | .card:hover { | ||||||
|     left: 0; |     box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); | ||||||
|     right: 0; | } | ||||||
|     top: 0; | 
 | ||||||
|     bottom: 0; | .card-active { | ||||||
|     cursor: pointer; |     border: 3px solid var(--highlight-two); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .info-container { | ||||||
|  |     padding: 2px 16px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | img { | ||||||
|  |     display: block; | ||||||
|  |     margin-left: auto; | ||||||
|  |     margin-right: auto; | ||||||
|  |     max-height: 200px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .measurement { | ||||||
|  |     width: 100px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .cage-selector { | .cage-selector { | ||||||
| @@ -152,29 +178,57 @@ label { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| input[type="checkbox"] { | input[type="checkbox"] { | ||||||
|     -webkit-appearance: none; |     display: none; | ||||||
|     position: relative; |  | ||||||
|     width: 100%; |  | ||||||
|     cursor: pointer; |  | ||||||
| } |  | ||||||
| input[type="checkbox"]:after { |  | ||||||
|     position: absolute; |  | ||||||
|     font-weight: 400; |  | ||||||
|     content: "0"; |  | ||||||
|     font-size: 18px; |  | ||||||
|     color: #478bfb; |  | ||||||
|     right: 10px; |  | ||||||
|     top: -5px; |  | ||||||
| } |  | ||||||
| input[type="checkbox"]:checked:after { |  | ||||||
|     font-weight: 900; |  | ||||||
|     content: "X"; |  | ||||||
|     color: #478bfb; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| .form-measurements { | .form-measurements { | ||||||
|     max-height: 90%; |     max-height: 90%; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .form-measurements input { | ||||||
|  |     width: 70%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| input.measurement { | input.measurement { | ||||||
|     margin-top: 5%; |     margin-top: 5%; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | .container-inputs { | ||||||
|  |     display: flex; | ||||||
|  |     flex-wrap: wrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .navigation-sticky { | ||||||
|  |     background-color: var(--secondary-light-one); | ||||||
|  |     color: var(--primary-light-one); | ||||||
|  |     padding: 16px; | ||||||
|  |     margin: 0; | ||||||
|  |     border-bottom-right-radius: 8px; | ||||||
|  |     border: none; | ||||||
|  |     font-weight: bold; | ||||||
|  |     width: 20%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .tooltip { | ||||||
|  |     position: relative; | ||||||
|  |     display: inline-block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tooltip .tooltiptext { | ||||||
|  |     visibility: hidden; | ||||||
|  |     width: 200px; | ||||||
|  |     background-color: var(--primary-dark-one); | ||||||
|  |     color: #ffffff; | ||||||
|  |     text-align: center; | ||||||
|  |     padding: 10px; | ||||||
|  |     border-radius: 5px; | ||||||
|  | 
 | ||||||
|  |     position: absolute; | ||||||
|  |     z-index: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tooltip:hover .tooltiptext { | ||||||
|  |     visibility: visible; | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								public/assets/favicon/android-chrome-192x192.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/assets/favicon/android-chrome-512x512.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 61 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/assets/favicon/apple-touch-icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/assets/favicon/favicon-16x16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 432 B | 
							
								
								
									
										
											BIN
										
									
								
								public/assets/favicon/favicon-32x32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.0 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/assets/favicon/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										1
									
								
								public/assets/favicon/site.webmanifest
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} | ||||||
							
								
								
									
										
											BIN
										
									
								
								public/assets/img/checked.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
							
								
								
									
										1
									
								
								public/assets/img/info.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg class="text-grey-dark" width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor"><path d="M9.00026 12.6C9.00026 12.6 9.00026 12.1224 9.00026 11.5333V8.86666C9.00026 8.57211 8.76148 8.33333 8.46693 8.33333H7.93359" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.73346 5.26666C8.58619 5.26666 8.4668 5.38605 8.4668 5.53333C8.4668 5.68061 8.58619 5.8 8.73346 5.8C8.88074 5.8 9.00013 5.68061 9.00013 5.53333C9.00013 5.38605 8.88074 5.26666 8.73346 5.26666V5.26666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M9 17C13.4183 17 17 13.4183 17 9C17 4.58172 13.4183 1 9 1C4.58172 1 1 4.58172 1 9C1 13.4183 4.58172 17 9 17Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg> | ||||||
| After Width: | Height: | Size: 834 B | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B | 
| Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB | 
| Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/assets/img/unchecked.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 639 B | 
							
								
								
									
										85
									
								
								public/assets/js/calculator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,85 @@ | |||||||
|  | const MINIMUM_BASE_AREA = 0.5; | ||||||
|  | const MINIMUM_AREA_THREE_RATS = 1.5; | ||||||
|  | const AREA_PER_ADDITIONAL_RAT = 0.25; | ||||||
|  | const MAXIMUM_FALL_HEIGHT = 0.5; | ||||||
|  | const MINIMUM_LENGTH_LONG_SIDE = 0.8; | ||||||
|  | const MINIMUM_LENGTH_SHORT_SIDE = 0.5; | ||||||
|  | const MINIMUM_FLOOR_HEIGHT = 0.3; | ||||||
|  |  | ||||||
|  | const FAILED_BASE_AREA = "base_area"; | ||||||
|  | const FAILED_OVERALL_AREA = "overall_area"; | ||||||
|  | const FAILED_FALL_HEIGHT = "fall_height"; | ||||||
|  | const FAILED_NUM_RATS = "num_rats"; | ||||||
|  | const FAILED_MINIMUM_LENGTH_LONG_SIDE = "length_long_side"; | ||||||
|  | const FAILED_MINIMUM_LENGTH_SHORT_SIDE= "length_short_side"; | ||||||
|  | const FAILED_FLOOR_HEIGHT = "floor_height" | ||||||
|  |  | ||||||
|  | const FAIL_CRITERIA = { | ||||||
|  |     [FAILED_BASE_AREA]: `Die Mindestgrundfläche des Käfigs muss ${MINIMUM_BASE_AREA}m² (also z.B. 100x50cm) betragen.`, | ||||||
|  |     [FAILED_OVERALL_AREA]: "Die Gesamtfläche im Käfig ist zu klein.", | ||||||
|  |     [FAILED_FALL_HEIGHT]: `Die mögliche Fallhöhe darf nicht mehr als ${(MAXIMUM_FALL_HEIGHT * 100).toFixed(0)}cm betragen.`, | ||||||
|  |     [FAILED_FLOOR_HEIGHT]: `Der Mindestabstand zwischen Ebenen muss ${(MINIMUM_FLOOR_HEIGHT * 100).toFixed(0)}cm betragen.`, | ||||||
|  |     [FAILED_NUM_RATS]: "Es müssen mindestens 3 Ratten zusammenleben, Paarhaltung ist nicht artgerecht.", | ||||||
|  |     [FAILED_MINIMUM_LENGTH_LONG_SIDE]: `Die lange Seite des Käfig muss mindestens ${(MINIMUM_LENGTH_LONG_SIDE * 100).toFixed(0)}cm lang sein um Rennen zu ermöglichen.`, | ||||||
|  |     [FAILED_MINIMUM_LENGTH_SHORT_SIDE]: `Die kurze Seite des Käfig muss mindestens ${(MINIMUM_LENGTH_SHORT_SIDE * 100).toFixed(0)}cm lang sein.`, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Dimensions { | ||||||
|  |     constructor(width, depth, height) { | ||||||
|  |         this.width = width; | ||||||
|  |         this.depth = depth; | ||||||
|  |         this.height = height; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     toString() { | ||||||
|  |         return `${this.width}x${this.depth}x${this.height}`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static fromDict(data) { | ||||||
|  |         const { width, depth,  height } = data; | ||||||
|  |         return new Dimensions(width, depth, height); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function overallAreaNeeded(numOfRats) { | ||||||
|  |     if (numOfRats < 3 || numOfRats > 15) { | ||||||
|  |         throw new Error("This formula works only from 3 to 15 rats"); | ||||||
|  |     } | ||||||
|  |     return MINIMUM_AREA_THREE_RATS + (numOfRats - 3) * AREA_PER_ADDITIONAL_RAT; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function cageCheck(dimensions, numRats, numFullFloors) { | ||||||
|  |     let failedCriteria = {}; | ||||||
|  |  | ||||||
|  |     if (numRats < 2 || numRats > 15) { | ||||||
|  |         failedCriteria[FAILED_NUM_RATS] = FAIL_CRITERIA[FAILED_NUM_RATS]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const baseArea = dimensions.depth * dimensions.width; | ||||||
|  |     if (baseArea < MINIMUM_BASE_AREA) { | ||||||
|  |         failedCriteria[FAILED_BASE_AREA] = FAIL_CRITERIA[FAILED_BASE_AREA]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const areaNeeded = overallAreaNeeded(numRats); | ||||||
|  |     if (baseArea * numFullFloors < areaNeeded) { | ||||||
|  |         failedCriteria[FAILED_OVERALL_AREA] = FAIL_CRITERIA[FAILED_OVERALL_AREA]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (dimensions.height / numFullFloors > MAXIMUM_FALL_HEIGHT) { | ||||||
|  |         failedCriteria[FAILED_FALL_HEIGHT] = FAIL_CRITERIA[FAILED_FALL_HEIGHT]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (dimensions.width < MINIMUM_LENGTH_LONG_SIDE && dimensions.depth < MINIMUM_LENGTH_LONG_SIDE) { | ||||||
|  |         failedCriteria[FAILED_MINIMUM_LENGTH_LONG_SIDE] = FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_LONG_SIDE]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (dimensions.width < MINIMUM_LENGTH_SHORT_SIDE || dimensions.depth < MINIMUM_LENGTH_SHORT_SIDE) { | ||||||
|  |         failedCriteria[FAILED_MINIMUM_LENGTH_SHORT_SIDE] = FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_SHORT_SIDE]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (dimensions.height / numFullFloors < MINIMUM_FLOOR_HEIGHT) { | ||||||
|  |         failedCriteria[FAILED_FLOOR_HEIGHT] = FAIL_CRITERIA[FAILED_FLOOR_HEIGHT]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return failedCriteria; | ||||||
|  | } | ||||||
							
								
								
									
										121
									
								
								public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,121 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8"> | ||||||
|  |     <title>Käfigrechner</title> | ||||||
|  |     <link rel="stylesheet" href="assets/css/style.css"> | ||||||
|  |     <script src="assets/js/calculator.js"></script> | ||||||
|  |  | ||||||
|  |     <link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png"> | ||||||
|  |     <link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png"> | ||||||
|  |     <link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png"> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |  | ||||||
|  | <div class="navigation-sticky"> | ||||||
|  |     <a href="https://notfellchen.org"> | ||||||
|  |         <b data-i18n-key="back-to-home">zurück zur Homepage</b> | ||||||
|  |     </a> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <img src="img/translation-icon@2x.png" class="translation-icon" /   > | ||||||
|  | <select data-i18n-switcher class="locale-switcher"> | ||||||
|  |     <option value="en">English</option> | ||||||
|  |     <option value="de">Deutsch</option> | ||||||
|  | </select> | ||||||
|  |  | ||||||
|  | <div class="content"> | ||||||
|  |     <h1 data-i18n-key="app-name">Käfigrechner</h1> | ||||||
|  |     <div class="container-form"> | ||||||
|  |         <div class="cards"> | ||||||
|  |             <div class="card" id="card-SavicSuiteRoyaleXL"> | ||||||
|  |                 <label for="SavicSuiteRoyaleXL"> | ||||||
|  |                     <input type="checkbox" id="SavicSuiteRoyaleXL"/> | ||||||
|  |                     <div class="card-photo"> | ||||||
|  |                         <img src="assets/img/savic-xl.jpeg"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="info-container"> | ||||||
|  |                         <h4><b>Savic Suite Royale XL</b></h4> | ||||||
|  |                     </div> | ||||||
|  |                 </label> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div class="card" id="card-SavicSuiteRoyale95Double"> | ||||||
|  |                 <label for="SavicSuiteRoyale95Double"> | ||||||
|  |                     <input type="checkbox" id="SavicSuiteRoyale95Double"/> | ||||||
|  |                     <div class="card-photo"> | ||||||
|  |                         <div> | ||||||
|  |                             <img src="assets/img/savic-95-double.jpg"> | ||||||
|  |                         </div> | ||||||
|  |                         <div class="info-container"> | ||||||
|  |                             <h4><b>Savic Suite Royale 95 Double</b></h4> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </label> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div class="card" id="card-TiakiKleintierkäfigEtagere"> | ||||||
|  |                 <label for="TiakiKleintierkäfigEtagere"> | ||||||
|  |                     <input type="checkbox" id="TiakiKleintierkäfigEtagere"/> | ||||||
|  |                     <div class="card-photo"> | ||||||
|  |                         <img src="assets/img/tiaki.jpeg"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="info-container"> | ||||||
|  |                         <h4><b>TIAKI Kleintierkäfig Étagère</b></h4> | ||||||
|  |                     </div> | ||||||
|  |                 </label> | ||||||
|  |             </div> | ||||||
|  |  | ||||||
|  |             <div class="card" id="card-ManualMeasurements"> | ||||||
|  |                 <label for="form-cage-measurements">Käfigmaße</label> | ||||||
|  |                 <form id="form-cage-measurements" class="form-measurements"> | ||||||
|  |                     <div class="input-measurement"> | ||||||
|  |                         <label for="width" data-i18n-key="width-cm">Breite (cm)</label> | ||||||
|  |                         <input class="measurement" type="number" id="width"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="input-measurement"> | ||||||
|  |                         <label for="depth" data-i18n-key="depth-cm">Tiefe (cm)</label> | ||||||
|  |                         <input class="measurement" type="number" id="depth"> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="input-measurement"> | ||||||
|  |                         <label for="height" data-i18n-key="height-cm">Höhe (cm)</label> | ||||||
|  |                         <input class="measurement" type="number" id="height"> | ||||||
|  |                     </div> | ||||||
|  |                 </form> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="container-inputs"> | ||||||
|  |         <div class="input-element"> | ||||||
|  |             <label for="numFullFloors" data-i18n-key="full-floors">Vollebenen</label> | ||||||
|  |             <div class="tooltip"> | ||||||
|  |                 <svg class="text-grey-dark" width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor"><path d="M9.00026 12.6C9.00026 12.6 9.00026 12.1224 9.00026 11.5333V8.86666C9.00026 8.57211 8.76148 8.33333 8.46693 8.33333H7.93359" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8.73346 5.26666C8.58619 5.26666 8.4668 5.38605 8.4668 5.53333C8.4668 5.68061 8.58619 5.8 8.73346 5.8C8.88074 5.8 9.00013 5.68061 9.00013 5.53333C9.00013 5.38605 8.88074 5.26666 8.73346 5.26666V5.26666" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M9 17C13.4183 17 17 13.4183 17 9C17 4.58172 13.4183 1 9 1C4.58172 1 1 4.58172 1 9C1 13.4183 4.58172 17 9 17Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg> | ||||||
|  |                 <span class="tooltiptext" data-i18n-key="full-floors-tooltip">Als Vollebenen zählen alle Ebenen die größer als 0.5m² sind, inklusive des Käfigbodens.</span> | ||||||
|  |             </div> | ||||||
|  |             <div class="ncontainer"> | ||||||
|  |                 <div class="input-group"> | ||||||
|  |                     <button id="decreaseFloorNum">-</button> | ||||||
|  |                     <input type="text" id="numFullFloors" value="3" readonly> | ||||||
|  |                     <button id="increaseFloorNum">+</button> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="input-element"> | ||||||
|  |             <div class="slidecontainer"> | ||||||
|  |                 <label for="numRats" id="labelNumRats" data-i18n-key="number-of-rats">Anzahl an Ratten ?</label> | ||||||
|  |                 <input type="range" min="3" max="15" value="4" class="slider" id="numRats"> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |  | ||||||
|  |     <div class="container output-element" id="resultsDiv"> | ||||||
|  |     </div> | ||||||
|  | </div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | <script src="./bundle.js"></script> | ||||||
|  |  | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										10
									
								
								public/lang/de.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |   "back-to-home": "Zurück zur Homepage", | ||||||
|  |   "app-name": "Käfigrechner", | ||||||
|  |   "number-of-rats": "Anzahl an Ratten", | ||||||
|  |   "full-floors": "Vollebenen", | ||||||
|  |   "width-cm": "Breite (cm)", | ||||||
|  |   "depth-cm": "Tiefe (cm)", | ||||||
|  |   "height-cm": "Höhe (cm)", | ||||||
|  |   "full-floors-tooltip": "Als Vollebenen zählen alle Ebenen die größer als 0.5m² sind, inklusive des Käfigbodens." | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								public/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |   "back-to-home": "Back to home", | ||||||
|  |   "app-name": "Cage Calculator", | ||||||
|  |   "number-of-rats": "Number of Rats", | ||||||
|  |   "full-floors": "Full floors", | ||||||
|  |   "width-cm": "Width (cm)", | ||||||
|  |   "depth-cm": "Depth (cm)", | ||||||
|  |   "height-cm": "Height (cm)", | ||||||
|  |   "full-floors-tooltip": "A full floor is each floor with a area greater than 0.5m², including the bottom of the cage." | ||||||
|  | } | ||||||
| @@ -1,89 +0,0 @@ | |||||||
| const MINIMUM_BASE_AREA = 0.5; |  | ||||||
| const MINIMUM_AREA_THREE_RATS = 1.8; |  | ||||||
| const AREA_PER_ADDITIONAL_RAT = 0.2; |  | ||||||
| const MAXIMUM_FALL_HEIGHT = 0.5; |  | ||||||
| const MINIMUM_LENGTH = 0.8; |  | ||||||
|  |  | ||||||
| const FAILED_BASE_AREA = "base_area"; |  | ||||||
| const FAILED_OVERALL_AREA = "overall_area"; |  | ||||||
| const FAILED_FALL_HEIGHT = "fall_height"; |  | ||||||
| const FAILED_NUM_RATS = "num_rats"; |  | ||||||
| const FAILED_LENGTH = "length"; |  | ||||||
|  |  | ||||||
| const FAIL_CRITERIA = { |  | ||||||
|     [FAILED_BASE_AREA]: `Die Mindestgrundfläche des Käfigs muss ${MINIMUM_BASE_AREA}m² (also z.B. 100x50cm) betragen.`, |  | ||||||
|     [FAILED_OVERALL_AREA]: "Die Gesamtfläche im Käfig ist zu klein.", |  | ||||||
|     [FAILED_FALL_HEIGHT]: `Die mögliche Fallhöhe darf nicht mehr als ${(MAXIMUM_FALL_HEIGHT * 100).toFixed(3)}cm betragen.`, |  | ||||||
|     [FAILED_NUM_RATS]: "Es müssen mindestens 3 Ratten zusammenleben, Paarhaltung ist nicht artgerecht.", |  | ||||||
|     [FAILED_LENGTH]: `Eine Seite des Käfig muss mindestens ${(MINIMUM_LENGTH * 100).toFixed(3)}cm lang sein um Rennen zu ermöglichen.`, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| class Dimensions { |  | ||||||
|     constructor(length, width, height) { |  | ||||||
|         this.length = length; |  | ||||||
|         this.width = width; |  | ||||||
|         this.height = height; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     toString() { |  | ||||||
|         return `${this.length}x${this.width}x${this.height}`; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     static fromDict(data) { |  | ||||||
|         const { length, width, height } = data; |  | ||||||
|         return new Dimensions(length, width, height); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function overallAreaNeeded(numOfRats) { |  | ||||||
|     if (numOfRats < 3 || numOfRats > 15) { |  | ||||||
|         throw new Error("This formula works only from 3 to 15 rats"); |  | ||||||
|     } |  | ||||||
|     return MINIMUM_AREA_THREE_RATS + (numOfRats - 3) * AREA_PER_ADDITIONAL_RAT; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function cageCheck(dimensions, numRats, numFullFloors) { |  | ||||||
|     let failedCriteria = {}; |  | ||||||
|  |  | ||||||
|     if (numRats < 2 || numRats > 15) { |  | ||||||
|         failedCriteria[FAILED_NUM_RATS] = FAIL_CRITERIA[FAILED_NUM_RATS]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const baseArea = dimensions.length * dimensions.width; |  | ||||||
|     if (baseArea < MINIMUM_BASE_AREA) { |  | ||||||
|         failedCriteria[FAILED_BASE_AREA] = FAIL_CRITERIA[FAILED_BASE_AREA]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const areaNeeded = overallAreaNeeded(numRats); |  | ||||||
|     if (baseArea * numFullFloors < areaNeeded) { |  | ||||||
|         failedCriteria[FAILED_OVERALL_AREA] = FAIL_CRITERIA[FAILED_OVERALL_AREA]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (dimensions.height / numFullFloors > MAXIMUM_FALL_HEIGHT) { |  | ||||||
|         failedCriteria[FAILED_FALL_HEIGHT] = FAIL_CRITERIA[FAILED_FALL_HEIGHT]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (dimensions.width < MINIMUM_LENGTH && dimensions.length < MINIMUM_LENGTH) { |  | ||||||
|         failedCriteria[FAILED_LENGTH] = FAIL_CRITERIA[FAILED_LENGTH]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return failedCriteria; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const expectedAreaPerNumRats = { 3: 1.8, 4: 2.0 }; |  | ||||||
| for (let numRats in expectedAreaPerNumRats) { |  | ||||||
|     console.log(`Area needed for ${numRats}: Calculated=${overallAreaNeeded(parseInt(numRats))}`); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const exampleCageChecks = [ |  | ||||||
|     [[], new Dimensions(1, 0.50, 1), 4, 4], |  | ||||||
|     [[FAIL_CRITERIA[FAILED_BASE_AREA]], new Dimensions(0.99, 0.50, 1), 3, 4], |  | ||||||
|     [[FAIL_CRITERIA[FAILED_OVERALL_AREA]], new Dimensions(0.99, 0.50, 1), 4, 4], |  | ||||||
|     [[FAIL_CRITERIA[FAILED_OVERALL_AREA]], new Dimensions(0.79, 0.79, 1), 4, 4], |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| for (let idx in exampleCageChecks) { |  | ||||||
|     let check = exampleCageChecks[idx]; |  | ||||||
|     console.log(`Example ${idx}: ${check[1]} n(rats): ${check[2]}, n(floors): ${check[3]}`); |  | ||||||
|     console.log(`Result: ${JSON.stringify(cageCheck(check[1], check[2], check[3]))}\n`); |  | ||||||
| } |  | ||||||
							
								
								
									
										154
									
								
								src/index.html
									
									
									
									
									
								
							
							
						
						| @@ -1,154 +0,0 @@ | |||||||
| <!DOCTYPE html> |  | ||||||
| <html lang="en" xmlns="http://www.w3.org/1999/html"> |  | ||||||
| <head> |  | ||||||
|     <meta charset="UTF-8"> |  | ||||||
|     <title>Knastrechner</title> |  | ||||||
|     <link rel="stylesheet" href="assets/css/style.css"> |  | ||||||
|     <script src="assets/calculator.js"></script> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
|  |  | ||||||
| <h1>Käfigrechner</h1> |  | ||||||
| <div class="content"> |  | ||||||
|     <div class="container-form"> |  | ||||||
|         <div class="cage-selector"> |  | ||||||
|             <div class="card"> |  | ||||||
|                 <div class="card-photo"> |  | ||||||
|                     <input type="checkbox" id="savic-xl"/> |  | ||||||
|                     <div> |  | ||||||
|                         <img src="assets/img/savic-xl.jpeg"> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <label for="savic-xl"> Savic Suite Royale XL</label> |  | ||||||
|             </div> |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             <div class="card"> |  | ||||||
|                 <div class="card-photo"> |  | ||||||
|                     <input type="checkbox" id="savic-95"/> |  | ||||||
|                     <div> |  | ||||||
|                         <img src="assets/img/savic-95-double.jpg"> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <label for="savic-95">Savic Suite Royale 95 Double</label> |  | ||||||
|             </div> |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             <div class="card"> |  | ||||||
|                 <div class="card-photo"> |  | ||||||
|                     <input type="checkbox" id="tiaki"/> |  | ||||||
|                     <div> |  | ||||||
|                         <img src="assets/img/tiaki.jpeg"> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <label>TIAKI Kleintierkäfig Étagère</label> |  | ||||||
|             </div> |  | ||||||
|  |  | ||||||
|             <div class="card"> |  | ||||||
|                 <div class="card-photo"> |  | ||||||
|                     <input type="checkbox" id="tiaki"/> |  | ||||||
|                     <form class="form-measurements"> |  | ||||||
|                         <input class="measurement" type="number" id="width"> |  | ||||||
|                         <label for="width">Breite</label> |  | ||||||
|  |  | ||||||
|                         <input class="measurement" type="number" id="depth"> |  | ||||||
|                         <label for="depth">Tiefe</label> |  | ||||||
|  |  | ||||||
|                         <input class="measurement" type="number" id="height"> |  | ||||||
|                         <label for="height">Höhe</label> |  | ||||||
|                     </form> |  | ||||||
|                 </div> |  | ||||||
|                 <label>Anderer Käfig</label> |  | ||||||
|             </div> |  | ||||||
|  |  | ||||||
|         </div> |  | ||||||
|         <div class="input-element"> |  | ||||||
|             <div class="slidecontainer"> |  | ||||||
|                 <label for="numRats" id="labelNumRats">Anzahl an Ratten ?</label> |  | ||||||
|                 <input type="range" min="1" max="20" value="4" class="slider" id="numRats"> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         <div class="input-element"> |  | ||||||
|             <label for="numFullFloors">Vollebenen</label> |  | ||||||
|             <div class="ncontainer"> |  | ||||||
|                 <div class="input-group"> |  | ||||||
|                     <button onclick="decreaseFloorNum()">-</button> |  | ||||||
|                     <input type="text" id="numFullFloors" value="3" readonly> |  | ||||||
|                     <button onclick="increaseFloorNum()">+</button> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|         </div> |  | ||||||
|         <div class="container output-element" id="resultsDiv"> |  | ||||||
|         </div> |  | ||||||
|     </div> |  | ||||||
|  |  | ||||||
|     <script> |  | ||||||
|  |  | ||||||
|         var labelNumRats = document.getElementById("labelNumRats"); |  | ||||||
|  |  | ||||||
|         var ratSlider = document.getElementById("numRats"); |  | ||||||
|  |  | ||||||
|         labelNumRats.innerHTML = `Anzahl an Ratten: ` + ratSlider.value; |  | ||||||
|  |  | ||||||
|         ratSlider.oninput = function () { |  | ||||||
|             labelNumRats.innerHTML = `Anzahl an Ratten: ` + this.value; |  | ||||||
|             update(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Full floor functions |  | ||||||
|         var fullFloorNum = document.getElementById("numFullFloors"); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         var savicSuiteRoyaleDim = new Dimensions(1, 0.5, 1); |  | ||||||
|  |  | ||||||
|         function getResultFromChecks(checks) { |  | ||||||
|             console.log(checks.length) |  | ||||||
|             if (Object.keys(checks).length > 0) { |  | ||||||
|                 const ul = document.createElement('ul'); |  | ||||||
|                 for (const key in checks) { |  | ||||||
|                     const li = document.createElement('li'); |  | ||||||
|                     li.textContent = `❌` + checks[key]; |  | ||||||
|                     ul.appendChild(li); |  | ||||||
|                 } |  | ||||||
|                 return ul; |  | ||||||
|             } else { |  | ||||||
|                 const p = document.createElement('p'); |  | ||||||
|                 p.innerHTML = "✅ Der Käfig erfüllt alle Kriterien!" |  | ||||||
|                 return p; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         function update() { |  | ||||||
|             var failed_checks = cageCheck(savicSuiteRoyaleDim, ratSlider.value, fullFloorNum.value); |  | ||||||
|             var resultsDiv = document.getElementById("resultsDiv"); |  | ||||||
|             resultsDiv.innerHTML = `<strong>Ergebnis</strong>`; |  | ||||||
|  |  | ||||||
|             const result = getResultFromChecks(failed_checks); |  | ||||||
|  |  | ||||||
|             resultsDiv.appendChild(result); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         function decreaseFloorNum() { |  | ||||||
|             var input = document.getElementById('numFullFloors'); |  | ||||||
|             var value = parseInt(input.value); |  | ||||||
|             if (value > 0) { |  | ||||||
|                 input.value = value - 1; |  | ||||||
|             } |  | ||||||
|             update(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         function increaseFloorNum() { |  | ||||||
|             var input = document.getElementById('numFullFloors'); |  | ||||||
|             var value = parseInt(input.value); |  | ||||||
|             input.value = value + 1; |  | ||||||
|             update(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         update(); |  | ||||||
|     </script> |  | ||||||
|  |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
							
								
								
									
										196
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,196 @@ | |||||||
|  | const inputDecreaseFloorNum = document.getElementById("decreaseFloorNum"); | ||||||
|  | inputDecreaseFloorNum.onclick = decreaseFloorNum; | ||||||
|  |  | ||||||
|  | const inputIncreaseFloorNum = document.getElementById("increaseFloorNum"); | ||||||
|  | inputIncreaseFloorNum.onclick = increaseFloorNum; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | const inputWidth = document.getElementById("width"); | ||||||
|  | inputWidth.onchange = updateViaManualMeasurements; | ||||||
|  | const inputDepth = document.getElementById("depth"); | ||||||
|  | inputDepth.onchange = updateViaManualMeasurements; | ||||||
|  | const inputHeight = document.getElementById("height"); | ||||||
|  | inputHeight.onchange = updateViaManualMeasurements; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | const selectSavicSuiteRoyaleXL = document.getElementById("SavicSuiteRoyaleXL"); | ||||||
|  | const selectSavicSuiteRoyale95Double = document.getElementById("SavicSuiteRoyale95Double"); | ||||||
|  | const selectTiakiKleintierkäfigEtagere = document.getElementById("TiakiKleintierkäfigEtagere"); | ||||||
|  |  | ||||||
|  | const cardSavicSuiteRoyaleXL = document.getElementById("card-SavicSuiteRoyaleXL"); | ||||||
|  | const cardSavicSuiteRoyale95Double = document.getElementById("card-SavicSuiteRoyale95Double"); | ||||||
|  | const cardTiakiKleintierkäfigEtagere = document.getElementById("card-TiakiKleintierkäfigEtagere"); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function markActiveCage(cageName) { | ||||||
|  |  | ||||||
|  |     cardSavicSuiteRoyaleXL.classList.remove("card-active"); | ||||||
|  |     cardSavicSuiteRoyale95Double.classList.remove("card-active"); | ||||||
|  |     cardTiakiKleintierkäfigEtagere.classList.remove("card-active"); | ||||||
|  |     if (cageName != "") { | ||||||
|  |         const activeCage = document.getElementById("card-" + cageName); | ||||||
|  |         activeCage.classList.add("card-active") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function updateCage(event) { | ||||||
|  |     selectSavicSuiteRoyaleXL.checked = false; | ||||||
|  |     selectSavicSuiteRoyale95Double.checked = false; | ||||||
|  |     selectTiakiKleintierkäfigEtagere.checked = false; | ||||||
|  |     const selectedCage = event.currentTarget | ||||||
|  |     selectedCage.checked = true; | ||||||
|  |     const cageName = selectedCage.id; | ||||||
|  |  | ||||||
|  |     console.log(cageName); | ||||||
|  |     var dim = getCageDimensions(cageName); | ||||||
|  |     inputWidth.value = dim.width; | ||||||
|  |     inputDepth.value = dim.depth; | ||||||
|  |     inputHeight.value = dim.height; | ||||||
|  |  | ||||||
|  |     markActiveCage(cageName); | ||||||
|  |  | ||||||
|  |     update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | selectSavicSuiteRoyaleXL.onchange = updateCage; | ||||||
|  | selectSavicSuiteRoyale95Double.onchange = updateCage; | ||||||
|  | selectTiakiKleintierkäfigEtagere.onchange = updateCage; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | var labelNumRats = document.getElementById("labelNumRats"); | ||||||
|  |  | ||||||
|  | var ratSlider = document.getElementById("numRats"); | ||||||
|  |  | ||||||
|  | labelNumRats.innerHTML = `Anzahl an Ratten: ` + ratSlider.value; | ||||||
|  |  | ||||||
|  | ratSlider.oninput = function () { | ||||||
|  |     labelNumRats.innerHTML = `Anzahl an Ratten: ` + this.value; | ||||||
|  |     update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Full floor functions | ||||||
|  | var fullFloorNum = document.getElementById("numFullFloors"); | ||||||
|  |  | ||||||
|  | function getCageDimensions(cageName) { | ||||||
|  |     console.log(cageName); | ||||||
|  |     if (cageName == "SavicSuiteRoyaleXL") { | ||||||
|  |         return new Dimensions(115, 67.5, 153); | ||||||
|  |     } | ||||||
|  |     if (cageName == "SavicSuiteRoyale95Double") { | ||||||
|  |         return new Dimensions(95, 63, 120); | ||||||
|  |     } | ||||||
|  |     if (cageName == "TiakiKleintierkäfigEtagere") { | ||||||
|  |         return new Dimensions(93.5, 63, 141.2); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function getResultFromChecks(checks) { | ||||||
|  |     console.log(checks.length) | ||||||
|  |     if (Object.keys(checks).length > 0) { | ||||||
|  |         const ul = document.createElement('ul'); | ||||||
|  |         for (const key in checks) { | ||||||
|  |             const li = document.createElement('li'); | ||||||
|  |             li.textContent = `❌` + checks[key]; | ||||||
|  |             ul.appendChild(li); | ||||||
|  |         } | ||||||
|  |         return ul; | ||||||
|  |     } else { | ||||||
|  |         const p = document.createElement('p'); | ||||||
|  |         p.innerHTML = "✅ Der Käfig erfüllt alle Kriterien!" | ||||||
|  |         return p; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function updateViaManualMeasurements() { | ||||||
|  |     markActiveCage("ManualMeasurements"); | ||||||
|  |     update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function update() { | ||||||
|  |     const width = inputWidth.value | ||||||
|  |     const depth = inputDepth.value | ||||||
|  |     const height = inputHeight.value | ||||||
|  |     const dimensions = new Dimensions(width / 100, depth / 100, height / 100); | ||||||
|  |     const failed_checks = cageCheck(dimensions, ratSlider.value, fullFloorNum.value); | ||||||
|  |     console.log(failed_checks); | ||||||
|  |     let resultsDiv = document.getElementById("resultsDiv"); | ||||||
|  |     resultsDiv.innerHTML = `<strong>Ergebnis</strong>`; | ||||||
|  |  | ||||||
|  |     const result = getResultFromChecks(failed_checks); | ||||||
|  |  | ||||||
|  |     resultsDiv.appendChild(result); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function decreaseFloorNum() { | ||||||
|  |     var input = document.getElementById('numFullFloors'); | ||||||
|  |     var value = parseInt(input.value); | ||||||
|  |     if (value > 0) { | ||||||
|  |         input.value = value - 1; | ||||||
|  |     } | ||||||
|  |     update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function increaseFloorNum() { | ||||||
|  |     var input = document.getElementById('numFullFloors'); | ||||||
|  |     var value = parseInt(input.value); | ||||||
|  |     input.value = value + 1; | ||||||
|  |     update(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | update(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import i18next from "i18next"; | ||||||
|  | import LanguageDetector from "i18next-browser-languagedetector"; | ||||||
|  | import HttpApi from "i18next-http-backend"; | ||||||
|  |  | ||||||
|  | async function initI18next() { | ||||||
|  |     // We use() the backend and await it to load | ||||||
|  |     // the translations from the network | ||||||
|  |     await i18next | ||||||
|  |         .use(HttpApi) | ||||||
|  |         .use(LanguageDetector) | ||||||
|  |         .init({ | ||||||
|  |             lng: "en", | ||||||
|  |             supportedLngs: ["en", "de"], | ||||||
|  |             nonExplicitSupportedLngs: true, | ||||||
|  |             fallbackLng: "en", | ||||||
|  |             debug: true, | ||||||
|  |             backend: { | ||||||
|  |                 loadPath: "/lang/{{lng}}.json", | ||||||
|  |             }, | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function translatePageElements() { | ||||||
|  |     const translatableElements = document.querySelectorAll( | ||||||
|  |         "[data-i18n-key]", | ||||||
|  |     ); | ||||||
|  |     translatableElements.forEach((el) => { | ||||||
|  |         const key = el.getAttribute("data-i18n-key"); | ||||||
|  |         el.innerHTML = i18next.t(key); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  | } | ||||||
|  | // ... | ||||||
|  |  | ||||||
|  | function bindLocaleSwitcher(initialValue) { | ||||||
|  |     const switcher = document.querySelector( | ||||||
|  |         "[data-i18n-switcher]", | ||||||
|  |     ); | ||||||
|  |     switcher.value = initialValue; | ||||||
|  |     switcher.onchange = (e) => { | ||||||
|  |         i18next | ||||||
|  |             .changeLanguage(e.target.value) | ||||||
|  |             .then(translatePageElements); | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | (async function () { | ||||||
|  |     await initI18next(); | ||||||
|  |     translatePageElements(); | ||||||
|  |     bindLocaleSwitcher(i18next.resolvedLanguage); | ||||||
|  | })(); | ||||||
							
								
								
									
										25
									
								
								webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | |||||||
|  | const path = require("path"); | ||||||
|  |  | ||||||
|  | const port = 3000; | ||||||
|  | const openBrowser = true; | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     entry: { | ||||||
|  |         app: ["./src/index.js"], | ||||||
|  |     }, | ||||||
|  |     output: { | ||||||
|  |         filename: "bundle.js", | ||||||
|  |         path: path.resolve(__dirname, "public"), | ||||||
|  |         publicPath: "/", | ||||||
|  |     }, | ||||||
|  |     mode: "development", | ||||||
|  |     devtool: "source-map", | ||||||
|  |     devServer: { | ||||||
|  |         port: port, | ||||||
|  |         open: openBrowser, | ||||||
|  |         historyApiFallback: { | ||||||
|  |             index: "index.html", | ||||||
|  |         }, | ||||||
|  |         static: "public", | ||||||
|  |     }, | ||||||
|  | }; | ||||||