modularize
This commit is contained in:
		
							parent
							
								
									315440e1c1
								
							
						
					
					
						commit
						1cfcb0a0b1
					
				
							
								
								
									
										1660
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										1660
									
								
								index.html
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										336
									
								
								webroot/css/components.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								webroot/css/components.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,336 @@
 | 
			
		||||
/* Calculator Styles */
 | 
			
		||||
.hex-calculator {
 | 
			
		||||
    background-color: #1a1a1a;
 | 
			
		||||
    min-height: fit-content;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calculator-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-display {
 | 
			
		||||
    background-color: #111111;
 | 
			
		||||
    border: 1px solid #404040;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    padding: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-input input {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border: 1px solid #555555;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    font-size: 16px;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    padding: 8px;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-conversions {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.conversion-row {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding: 3px 0;
 | 
			
		||||
    border-bottom: 1px solid #333333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.conversion-row:last-child {
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.conv-label {
 | 
			
		||||
    color: #888888;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    min-width: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.conv-value {
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    word-break: break-all;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-buttons {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    gap: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-row {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(5, 1fr);
 | 
			
		||||
    gap: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn {
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border: 1px solid #4a4a4a;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    min-height: 32px;
 | 
			
		||||
    padding: 4px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    transition: all 0.2s;
 | 
			
		||||
    user-select: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn:hover {
 | 
			
		||||
    background-color: #3a3a3a;
 | 
			
		||||
    border-color: #666666;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn:active {
 | 
			
		||||
    background-color: #444444;
 | 
			
		||||
    transform: translateY(1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.number {
 | 
			
		||||
    background-color: #333333;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.number:hover {
 | 
			
		||||
    background-color: #444444;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.operation {
 | 
			
		||||
    background-color: #2a4a2a;
 | 
			
		||||
    color: #88cc88;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.operation:hover {
 | 
			
		||||
    background-color: #3a5a3a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.clear {
 | 
			
		||||
    background-color: #4a2a4a;
 | 
			
		||||
    color: #cc88cc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.clear:hover {
 | 
			
		||||
    background-color: #5a3a5a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.equals {
 | 
			
		||||
    background-color: #2a2a4a;
 | 
			
		||||
    color: #8888cc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.equals:hover {
 | 
			
		||||
    background-color: #3a3a5a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.backspace {
 | 
			
		||||
    background-color: #4a4a2a;
 | 
			
		||||
    color: #cccc88;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.backspace:hover {
 | 
			
		||||
    background-color: #5a5a3a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calc-btn.wide {
 | 
			
		||||
    grid-column: span 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Input Groups */
 | 
			
		||||
.input-group {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-group label {
 | 
			
		||||
    margin-bottom: 6px;
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    font-size: 0.95em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.unit-indicator {
 | 
			
		||||
    color: #88cc88;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    font-size: 0.85em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-group input {
 | 
			
		||||
    padding: 12px;
 | 
			
		||||
    background-color: #333333;
 | 
			
		||||
    border: 1px solid #4a4a4a;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    transition: border-color 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-group input:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-color: #666666;
 | 
			
		||||
    background-color: #383838;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.input-group input.error {
 | 
			
		||||
    border-color: #ff6666;
 | 
			
		||||
    background-color: #332222;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error-message {
 | 
			
		||||
    color: #ff8888;
 | 
			
		||||
    font-size: 0.8em;
 | 
			
		||||
    margin-top: 4px;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Result Items */
 | 
			
		||||
.result-item {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    padding: 10px 0;
 | 
			
		||||
    border-bottom: 1px solid #3a3a3a;
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-item:last-child {
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-item.available {
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-item.unavailable {
 | 
			
		||||
    opacity: 0.5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-label {
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    cursor: help;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-value-container {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-value {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    min-width: 120px;
 | 
			
		||||
    cursor: help;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.copy-btn {
 | 
			
		||||
    background: none;
 | 
			
		||||
    border: none;
 | 
			
		||||
    color: #888888;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    padding: 4px;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    transition: all 0.2s;
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.copy-btn:hover {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    background-color: #404040;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.copy-btn:active {
 | 
			
		||||
    color: #88cc88;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Result Groups */
 | 
			
		||||
.result-group {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    border: 1px solid #3a3a3a;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.result-group h3 {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
    border-bottom: 1px solid #3a3a3a;
 | 
			
		||||
    padding-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Formula Styles */
 | 
			
		||||
.formula-box {
 | 
			
		||||
    background-color: #151515;
 | 
			
		||||
    border: 1px solid #404040;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    padding: 15px;
 | 
			
		||||
    margin: 10px 0;
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.formula {
 | 
			
		||||
    margin: 8px 0;
 | 
			
		||||
    color: #d0d0d0;
 | 
			
		||||
    word-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.calculated {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Tooltip */
 | 
			
		||||
.tooltip {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    background-color: #1a1a1a;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    padding: 8px 12px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    font-size: 0.85em;
 | 
			
		||||
    border: 1px solid #555555;
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
    max-width: 400px;
 | 
			
		||||
    word-wrap: break-word;
 | 
			
		||||
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
 | 
			
		||||
    font-family: 'Consolas', monospace;
 | 
			
		||||
    line-height: 1.4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Mobile Responsive */
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .result-item {
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: flex-start;
 | 
			
		||||
        gap: 5px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .result-value {
 | 
			
		||||
        text-align: left;
 | 
			
		||||
        min-width: auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										234
									
								
								webroot/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								webroot/css/main.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,234 @@
 | 
			
		||||
* {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: 'Consolas', 'Courier New', monospace;
 | 
			
		||||
    background-color: #1a1a1a;
 | 
			
		||||
    color: #e0e0e0;
 | 
			
		||||
    line-height: 1.6;
 | 
			
		||||
    min-height: 100vh;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Header */
 | 
			
		||||
.header {
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border-bottom: 2px solid #3a3a3a;
 | 
			
		||||
    padding: 15px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header-content {
 | 
			
		||||
    max-width: 1400px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    padding: 0 20px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logo {
 | 
			
		||||
    font-size: 1.5em;
 | 
			
		||||
    font-weight: bold;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav a {
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    transition: color 0.2s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav a:hover {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Main Content */
 | 
			
		||||
.main-content {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container {
 | 
			
		||||
    max-width: 1400px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1 {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    margin-bottom: 30px;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    font-size: 2.2em;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Tabs */
 | 
			
		||||
.tabs {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border-radius: 6px 6px 0 0;
 | 
			
		||||
    border: 1px solid #3a3a3a;
 | 
			
		||||
    border-bottom: none;
 | 
			
		||||
    overflow-x: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab {
 | 
			
		||||
    padding: 15px 25px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border-right: 1px solid #3a3a3a;
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    transition: all 0.2s;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    min-width: 120px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab:last-child {
 | 
			
		||||
    border-right: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab:hover {
 | 
			
		||||
    background-color: #353535;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab.active {
 | 
			
		||||
    background-color: #3a3a3a;
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    border-bottom: 2px solid #666666;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-content {
 | 
			
		||||
    display: none;
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border: 1px solid #3a3a3a;
 | 
			
		||||
    border-radius: 0 0 6px 6px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-content.active {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Sections */
 | 
			
		||||
.section {
 | 
			
		||||
    background-color: #252525;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    border: 1px solid #3a3a3a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.section h2 {
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    margin-bottom: 15px;
 | 
			
		||||
    border-bottom: 1px solid #3a3a3a;
 | 
			
		||||
    padding-bottom: 8px;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    font-size: 1.3em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.constants {
 | 
			
		||||
    background-color: #202020;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.results {
 | 
			
		||||
    background-color: #202020;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.formulas {
 | 
			
		||||
    background-color: #1a1a1a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Layout */
 | 
			
		||||
.constants-calculator-container {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: 1fr 350px;
 | 
			
		||||
    gap: 20px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 1024px) {
 | 
			
		||||
    .constants-calculator-container {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
        gap: 15px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Input Grid */
 | 
			
		||||
.input-grid {
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
 | 
			
		||||
    gap: 15px;
 | 
			
		||||
    margin: 20px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
    .input-grid {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
        gap: 12px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    .constants-calculator-container {
 | 
			
		||||
        grid-template-columns: 1fr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Footer */
 | 
			
		||||
.footer {
 | 
			
		||||
    background-color: #2a2a2a;
 | 
			
		||||
    border-top: 1px solid #3a3a3a;
 | 
			
		||||
    padding: 20px 0;
 | 
			
		||||
    margin-top: 40px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footer-content {
 | 
			
		||||
    max-width: 1400px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    padding: 0 20px;
 | 
			
		||||
    display: grid;
 | 
			
		||||
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
 | 
			
		||||
    gap: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footer-section h3 {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    font-size: 1.1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footer-section p,
 | 
			
		||||
.footer-section a {
 | 
			
		||||
    color: #cccccc;
 | 
			
		||||
    font-size: 0.9em;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footer-section a:hover {
 | 
			
		||||
    color: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.footer-bottom {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    padding-top: 20px;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
    border-top: 1px solid #3a3a3a;
 | 
			
		||||
    color: #888888;
 | 
			
		||||
    font-size: 0.85em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error {
 | 
			
		||||
    color: #ff8888;
 | 
			
		||||
    font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								webroot/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								webroot/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="de">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Dateisystem-Offset-Rechner</title>
 | 
			
		||||
    <link rel="stylesheet" href="css/main.css">
 | 
			
		||||
    <link rel="stylesheet" href="css/components.css">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <header class="header">
 | 
			
		||||
        <div class="header-content">
 | 
			
		||||
            <div class="logo">Dateisystem-Offset-Rechner</div>
 | 
			
		||||
            <nav class="nav">
 | 
			
		||||
                <a href="#home">Start</a>
 | 
			
		||||
                <a href="mailto:mstoeck3@hs-mittweida.de">Kontakt</a>
 | 
			
		||||
            </nav>
 | 
			
		||||
        </div>
 | 
			
		||||
    </header>
 | 
			
		||||
 | 
			
		||||
    <main class="main-content">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <h1>Dateisystem-Offset-Rechner</h1>
 | 
			
		||||
 | 
			
		||||
            <!-- Dynamic tabs will be inserted here -->
 | 
			
		||||
            <div id="filesystem-tabs" class="tabs"></div>
 | 
			
		||||
 | 
			
		||||
            <!-- Dynamic tab content will be inserted here -->
 | 
			
		||||
            <div id="filesystem-content"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </main>
 | 
			
		||||
 | 
			
		||||
    <footer class="footer">
 | 
			
		||||
        <div class="footer-content">
 | 
			
		||||
            <div class="footer-section">
 | 
			
		||||
                <h3>Über</h3>
 | 
			
		||||
                <p>Dateisystem Offset-Rechner für Bildung und Forensik.</p>
 | 
			
		||||
                <p>Version 0.4.0</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="footer-section">
 | 
			
		||||
                <h3>Support</h3>
 | 
			
		||||
                <a href="mailto:mstoeck3@hs-mittweida.de">Kontakt</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="footer-bottom">
 | 
			
		||||
            <p>© 2025 Dateisystem Offset-Rechner. Public Domain unter BSD-3-Clause.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    </footer>
 | 
			
		||||
 | 
			
		||||
    <script type="module" src="js/main.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										243
									
								
								webroot/js/calculator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								webroot/js/calculator.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,243 @@
 | 
			
		||||
// Hex calculator functionality
 | 
			
		||||
 | 
			
		||||
export class Calculator {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.state = {
 | 
			
		||||
            currentValue: 0,
 | 
			
		||||
            previousValue: 0,
 | 
			
		||||
            operation: null,
 | 
			
		||||
            waitingForOperand: false
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateDisplay() {
 | 
			
		||||
        const value = Math.max(0, Math.min(Math.floor(this.state.currentValue), Number.MAX_SAFE_INTEGER));
 | 
			
		||||
 | 
			
		||||
        const hexStr = '0x' + value.toString(16).toUpperCase();
 | 
			
		||||
        const decStr = value.toString(10);
 | 
			
		||||
        const binStr = '0b' + value.toString(2);
 | 
			
		||||
 | 
			
		||||
        // Update all calculator displays (for current active tab)
 | 
			
		||||
        const inputs = document.querySelectorAll('[id*="calcInput"]');
 | 
			
		||||
        const hexElements = document.querySelectorAll('[id*="hexValue"]');
 | 
			
		||||
        const decElements = document.querySelectorAll('[id*="decValue"]');
 | 
			
		||||
        const binElements = document.querySelectorAll('[id*="binValue"]');
 | 
			
		||||
 | 
			
		||||
        inputs.forEach(input => input.value = hexStr);
 | 
			
		||||
        hexElements.forEach(el => el.textContent = hexStr);
 | 
			
		||||
        decElements.forEach(el => el.textContent = decStr);
 | 
			
		||||
        binElements.forEach(el => el.textContent = binStr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    input(digit) {
 | 
			
		||||
        if (this.state.waitingForOperand) {
 | 
			
		||||
            this.state.currentValue = 0;
 | 
			
		||||
            this.state.waitingForOperand = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const digitValue = parseInt(digit, 16);
 | 
			
		||||
        if (isNaN(digitValue)) return;
 | 
			
		||||
 | 
			
		||||
        const newValue = this.state.currentValue * 16 + digitValue;
 | 
			
		||||
        if (newValue <= Number.MAX_SAFE_INTEGER && newValue >= 0) {
 | 
			
		||||
            this.state.currentValue = newValue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.updateDisplay();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    operation(operation) {
 | 
			
		||||
        if (this.state.previousValue !== 0 && !this.state.waitingForOperand) {
 | 
			
		||||
            this.equals(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.state.previousValue = this.state.currentValue;
 | 
			
		||||
        this.state.operation = operation;
 | 
			
		||||
        this.state.waitingForOperand = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    equals(updateDisplay = true) {
 | 
			
		||||
        if (this.state.operation && this.state.previousValue !== null) {
 | 
			
		||||
            const prev = Math.floor(this.state.previousValue);
 | 
			
		||||
            const curr = Math.floor(this.state.currentValue);
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                let result = 0;
 | 
			
		||||
                switch (this.state.operation) {
 | 
			
		||||
                    case '+':
 | 
			
		||||
                        result = prev + curr;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case '-':
 | 
			
		||||
                        result = prev - curr;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case '*':
 | 
			
		||||
                        result = prev * curr;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case '/':
 | 
			
		||||
                        if (curr === 0) {
 | 
			
		||||
                            alert('Division durch Null!');
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        result = Math.floor(prev / curr);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'MOD':
 | 
			
		||||
                        if (curr === 0) {
 | 
			
		||||
                            alert('Modulo durch Null!');
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        result = prev % curr;
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.state.currentValue = Math.max(0, Math.min(result, Number.MAX_SAFE_INTEGER));
 | 
			
		||||
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                alert('Berechnungsfehler: ' + error.message);
 | 
			
		||||
                this.state.currentValue = 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.state.operation = null;
 | 
			
		||||
            this.state.previousValue = 0;
 | 
			
		||||
            this.state.waitingForOperand = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (updateDisplay) {
 | 
			
		||||
            this.updateDisplay();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    clear() {
 | 
			
		||||
        this.state.currentValue = 0;
 | 
			
		||||
        this.state.previousValue = 0;
 | 
			
		||||
        this.state.operation = null;
 | 
			
		||||
        this.state.waitingForOperand = false;
 | 
			
		||||
        this.updateDisplay();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    backspace() {
 | 
			
		||||
        if (!this.state.waitingForOperand) {
 | 
			
		||||
            this.state.currentValue = Math.floor(this.state.currentValue / 16);
 | 
			
		||||
            this.updateDisplay();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupEventListeners() {
 | 
			
		||||
        // Button clicks
 | 
			
		||||
        document.addEventListener('click', (e) => {
 | 
			
		||||
            if (e.target.classList.contains('calc-btn')) {
 | 
			
		||||
                const action = e.target.getAttribute('onclick');
 | 
			
		||||
                if (action) {
 | 
			
		||||
                    // Parse the onclick attribute to determine the action
 | 
			
		||||
                    if (action.includes('calcInput')) {
 | 
			
		||||
                        const match = action.match(/calcInput\('([^']+)'\)/);
 | 
			
		||||
                        if (match) this.input(match[1]);
 | 
			
		||||
                    } else if (action.includes('calcOperation')) {
 | 
			
		||||
                        const match = action.match(/calcOperation\('([^']+)'\)/);
 | 
			
		||||
                        if (match) this.operation(match[1]);
 | 
			
		||||
                    } else if (action.includes('calcEquals')) {
 | 
			
		||||
                        this.equals();
 | 
			
		||||
                    } else if (action.includes('calcClear')) {
 | 
			
		||||
                        this.clear();
 | 
			
		||||
                    } else if (action.includes('calcBackspace')) {
 | 
			
		||||
                        this.backspace();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Keyboard support
 | 
			
		||||
        document.addEventListener('keydown', (e) => {
 | 
			
		||||
            if (e.target.tagName === 'INPUT' && !e.target.readOnly) return;
 | 
			
		||||
 | 
			
		||||
            const key = e.key.toUpperCase();
 | 
			
		||||
 | 
			
		||||
            if (/^[0-9A-F]$/.test(key)) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.input(key);
 | 
			
		||||
            } else if (key === '+') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.operation('+');
 | 
			
		||||
            } else if (key === '-') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.operation('-');
 | 
			
		||||
            } else if (key === '*') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.operation('*');
 | 
			
		||||
            } else if (key === '/') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.operation('/');
 | 
			
		||||
            } else if (key === 'ENTER' || key === '=') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.equals();
 | 
			
		||||
            } else if (key === 'ESCAPE' || key === 'C') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.clear();
 | 
			
		||||
            } else if (key === 'BACKSPACE') {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                this.backspace();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    generateHTML() {
 | 
			
		||||
        return `
 | 
			
		||||
            <div class="calculator-container">
 | 
			
		||||
                <div class="calc-display">
 | 
			
		||||
                    <div class="calc-input">
 | 
			
		||||
                        <input type="text" id="calcInput" placeholder="0x0" readonly>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="calc-conversions">
 | 
			
		||||
                        <div class="conversion-row">
 | 
			
		||||
                            <span class="conv-label">HEX:</span>
 | 
			
		||||
                            <span class="conv-value" id="hexValue">0x0</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="conversion-row">
 | 
			
		||||
                            <span class="conv-label">DEC:</span>
 | 
			
		||||
                            <span class="conv-value" id="decValue">0</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="conversion-row">
 | 
			
		||||
                            <span class="conv-label">BIN:</span>
 | 
			
		||||
                            <span class="conv-value" id="binValue">0b0</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="calc-buttons">
 | 
			
		||||
                    <div class="calc-row">
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('A')">A</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('B')">B</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('C')">C</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('D')">D</button>
 | 
			
		||||
                        <button class="calc-btn clear" onclick="calcClear()">C</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="calc-row">
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('E')">E</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('F')">F</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('7')">7</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('8')">8</button>
 | 
			
		||||
                        <button class="calc-btn operation" onclick="calcOperation('/')">/</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="calc-row">
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('4')">4</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('5')">5</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('6')">6</button>
 | 
			
		||||
                        <button class="calc-btn operation" onclick="calcOperation('*')">*</button>
 | 
			
		||||
                        <button class="calc-btn operation" onclick="calcOperation('MOD')">MOD</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="calc-row">
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('1')">1</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('2')">2</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('3')">3</button>
 | 
			
		||||
                        <button class="calc-btn operation" onclick="calcOperation('-')">-</button>
 | 
			
		||||
                        <button class="calc-btn operation" onclick="calcOperation('+')">+</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="calc-row">
 | 
			
		||||
                        <button class="calc-btn number wide" onclick="calcInput('0')">0</button>
 | 
			
		||||
                        <button class="calc-btn number" onclick="calcInput('9')">9</button>
 | 
			
		||||
                        <button class="calc-btn backspace" onclick="calcBackspace()">⌫</button>
 | 
			
		||||
                        <button class="calc-btn equals" onclick="calcEquals()">=</button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										170
									
								
								webroot/js/filesystems/base.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								webroot/js/filesystems/base.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,170 @@
 | 
			
		||||
// Base filesystem class that defines the common interface for all filesystem implementations
 | 
			
		||||
 | 
			
		||||
import { parseHex, validateInput, checkDependencies, updateResultItem } from '../utils.js';
 | 
			
		||||
 | 
			
		||||
export class BaseFilesystem {
 | 
			
		||||
    constructor(name, variants = []) {
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        this.variants = variants; // Array of variant configurations
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Abstract method to be implemented by subclasses
 | 
			
		||||
    getVariants() {
 | 
			
		||||
        return this.variants;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Generate HTML for constants section
 | 
			
		||||
    generateConstantsHTML(variantId) {
 | 
			
		||||
        const variant = this.variants.find(v => v.id === variantId);
 | 
			
		||||
        if (!variant) return '';
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
            <div class="section constants">
 | 
			
		||||
                <h2>Konstanten</h2>
 | 
			
		||||
                <div class="input-grid">
 | 
			
		||||
                    ${variant.constants.map(constant => `
 | 
			
		||||
                        <div class="input-group">
 | 
			
		||||
                            <label for="${constant.id}">${constant.label} <span class="unit-indicator">(${constant.unit})</span>:</label>
 | 
			
		||||
                            <input type="text" id="${constant.id}" value="${constant.default}" placeholder="${constant.default}">
 | 
			
		||||
                            <div class="error-message" id="${constant.id}-error"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    `).join('')}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Generate HTML for input parameters section
 | 
			
		||||
    generateInputsHTML(variantId) {
 | 
			
		||||
        const variant = this.variants.find(v => v.id === variantId);
 | 
			
		||||
        if (!variant) return '';
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
            <div class="section">
 | 
			
		||||
                <h2>Eingabeparameter</h2>
 | 
			
		||||
                <div class="input-grid">
 | 
			
		||||
                    ${variant.inputs.map(input => `
 | 
			
		||||
                        <div class="input-group">
 | 
			
		||||
                            <label for="${input.id}">${input.label} <span class="unit-indicator">(${input.unit})</span>:</label>
 | 
			
		||||
                            <input type="text" id="${input.id}" placeholder="${input.placeholder}">
 | 
			
		||||
                            <div class="error-message" id="${input.id}-error"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    `).join('')}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Generate HTML for results section
 | 
			
		||||
    generateResultsHTML(variantId) {
 | 
			
		||||
        const variant = this.variants.find(v => v.id === variantId);
 | 
			
		||||
        if (!variant) return '';
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
            <div class="section results">
 | 
			
		||||
                <h2>Berechnete Werte</h2>
 | 
			
		||||
                ${variant.resultGroups.map(group => `
 | 
			
		||||
                    <div class="result-group">
 | 
			
		||||
                        <h3>${group.name}</h3>
 | 
			
		||||
                        ${group.results.map(result => `
 | 
			
		||||
                            <div class="result-item" data-deps="${result.dependencies.join(',')}">
 | 
			
		||||
                                <span class="result-label" data-formula="${result.formula}">${result.label}:</span>
 | 
			
		||||
                                <div class="result-value-container">
 | 
			
		||||
                                    <span class="result-value" id="${result.id}">-</span>
 | 
			
		||||
                                    <button class="copy-btn" onclick="copyToClipboard('${result.id}')">📋</button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        `).join('')}
 | 
			
		||||
                    </div>
 | 
			
		||||
                `).join('')}
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Generate HTML for formulas section
 | 
			
		||||
    generateFormulasHTML(variantId) {
 | 
			
		||||
        const variant = this.variants.find(v => v.id === variantId);
 | 
			
		||||
        if (!variant) return '';
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
            <div class="section formulas">
 | 
			
		||||
                <h2>Berechnungsformeln ${this.name}</h2>
 | 
			
		||||
                <div class="formula-box">
 | 
			
		||||
                    ${variant.formulas.map(formula => `
 | 
			
		||||
                        <div class="formula"><span class="calculated">${formula.name}</span> = ${formula.expression}</div>
 | 
			
		||||
                    `).join('')}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Generate complete tab content HTML
 | 
			
		||||
    generateTabContentHTML(variantId, calculatorHTML) {
 | 
			
		||||
        return `
 | 
			
		||||
            <div class="tab-content" id="${variantId}">
 | 
			
		||||
                <div class="constants-calculator-container">
 | 
			
		||||
                    ${this.generateConstantsHTML(variantId)}
 | 
			
		||||
                    <div class="section hex-calculator">
 | 
			
		||||
                        <h2>Hex-Rechner</h2>
 | 
			
		||||
                        ${calculatorHTML}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                ${this.generateInputsHTML(variantId)}
 | 
			
		||||
                ${this.generateResultsHTML(variantId)}
 | 
			
		||||
                ${this.generateFormulasHTML(variantId)}
 | 
			
		||||
            </div>
 | 
			
		||||
        `;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Abstract method for calculations - to be implemented by subclasses
 | 
			
		||||
    calculate(variantId) {
 | 
			
		||||
        throw new Error('calculate method must be implemented by subclass');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Setup input event listeners for this filesystem
 | 
			
		||||
    setupInputListeners(variantId) {
 | 
			
		||||
        const variant = this.variants.find(v => v.id === variantId);
 | 
			
		||||
        if (!variant) return;
 | 
			
		||||
 | 
			
		||||
        const allInputIds = [
 | 
			
		||||
            ...variant.constants.map(c => c.id),
 | 
			
		||||
            ...variant.inputs.map(i => i.id)
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        allInputIds.forEach(inputId => {
 | 
			
		||||
            const input = document.getElementById(inputId);
 | 
			
		||||
            if (input) {
 | 
			
		||||
                input.addEventListener('input', () => {
 | 
			
		||||
                    validateInput(inputId, input.value);
 | 
			
		||||
                    this.calculate(variantId);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get all input values for a variant
 | 
			
		||||
    getInputValues(variantId) {
 | 
			
		||||
        const variant = this.variants.find(v => v.id === variantId);
 | 
			
		||||
        if (!variant) return {};
 | 
			
		||||
 | 
			
		||||
        const values = {};
 | 
			
		||||
        
 | 
			
		||||
        // Get constants
 | 
			
		||||
        variant.constants.forEach(constant => {
 | 
			
		||||
            const element = document.getElementById(constant.id);
 | 
			
		||||
            if (element) {
 | 
			
		||||
                values[constant.id] = parseHex(element.value) || 0;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Get inputs
 | 
			
		||||
        variant.inputs.forEach(input => {
 | 
			
		||||
            const element = document.getElementById(input.id);
 | 
			
		||||
            if (element) {
 | 
			
		||||
                values[input.id] = parseHex(element.value);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return values;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										398
									
								
								webroot/js/filesystems/fat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								webroot/js/filesystems/fat.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,398 @@
 | 
			
		||||
// FAT filesystem implementation (FAT12/16 and FAT32)
 | 
			
		||||
 | 
			
		||||
import { BaseFilesystem } from './base.js';
 | 
			
		||||
import { checkDependencies, updateResultItem } from '../utils.js';
 | 
			
		||||
 | 
			
		||||
export class FATFilesystem extends BaseFilesystem {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('FAT', [
 | 
			
		||||
            {
 | 
			
		||||
                id: 'fat1216',
 | 
			
		||||
                name: 'FAT12/16',
 | 
			
		||||
                constants: [
 | 
			
		||||
                    { id: 'baseOffset1216', label: 'Basis-Offset', unit: 'Bytes', default: '0x0' },
 | 
			
		||||
                    { id: 'sectorSize1216', label: 'Sektorgröße', unit: 'Bytes', default: '0x200' }
 | 
			
		||||
                ],
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    { id: 'reservedSectors1216', label: 'Reservierte Sektoren', unit: 'Anzahl', placeholder: '0x1' },
 | 
			
		||||
                    { id: 'numFATs1216', label: 'Anzahl FATs', unit: 'Anzahl', placeholder: '0x2' },
 | 
			
		||||
                    { id: 'fatSizeSectors1216', label: 'FAT-Größe', unit: 'Sektoren', placeholder: '0x4000' },
 | 
			
		||||
                    { id: 'maxRootEntries1216', label: 'Max. Root-Einträge', unit: 'Anzahl', placeholder: '0x200' },
 | 
			
		||||
                    { id: 'partitionSizeInSectors1216', label: 'Partitionsgröße', unit: 'Sektoren', placeholder: '0x4015' },
 | 
			
		||||
                    { id: 'clusterSizeSectors1216', label: 'Clustergröße', unit: 'Sektoren', placeholder: '0x2' },
 | 
			
		||||
                    { id: 'clusterNumber1216', label: 'Cluster-Nummer', unit: 'Nummer', placeholder: '0x2' }
 | 
			
		||||
                ],
 | 
			
		||||
                resultGroups: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'FAT-Struktur',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'fatStart1216', label: 'FAT-Bereich Anfang (Bytes)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216'], formula: 'reservedSectors × sectorSize + baseOffset' },
 | 
			
		||||
                            { id: 'fatStartSector1216', label: 'FAT-Bereich Anfang (Sektor)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216'], formula: 'fatStart ÷ sectorSize' },
 | 
			
		||||
                            { id: 'fatSize1216', label: 'FAT-Bereich Größe (Bytes)', dependencies: ['numFATs1216', 'fatSizeSectors1216', 'sectorSize1216'], formula: 'numFATs × fatSizeSectors × sectorSize' },
 | 
			
		||||
                            { id: 'fat2Start1216', label: 'FAT2 Anfang (Bytes)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'fatSizeSectors1216'], formula: 'fatStart + (fatSizeSectors × sectorSize)' },
 | 
			
		||||
                            { id: 'fatEnd1216', label: 'FAT-Bereich Ende (Bytes)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216'], formula: 'fatStart + fatSize' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'Root-Directory',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'rootDirStart1216', label: 'Root-Directory Anfang (Bytes)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216'], formula: 'fatEnd' },
 | 
			
		||||
                            { id: 'rootDirSize1216', label: 'Root-Directory Größe (Bytes)', dependencies: ['maxRootEntries1216'], formula: 'maxRootEntries × 0x20' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'Daten-Bereich',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'dataStart1216', label: 'Daten-Bereich Anfang (Bytes)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216', 'maxRootEntries1216'], formula: 'fatEnd + rootDirSize' },
 | 
			
		||||
                            { id: 'dataStartSector1216', label: 'Daten-Bereich Anfang (Sektor)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216', 'maxRootEntries1216'], formula: 'dataStart ÷ sectorSize' },
 | 
			
		||||
                            { id: 'dataSize1216', label: 'Daten-Bereich Größe (Bytes)', dependencies: ['partitionSizeInSectors1216', 'sectorSize1216', 'reservedSectors1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216', 'maxRootEntries1216'], formula: '(partitionSizeInSectors × sectorSize) - (dataStart - baseOffset)' },
 | 
			
		||||
                            { id: 'clusterSize1216', label: 'Cluster-Größe (Bytes)', dependencies: ['sectorSize1216', 'clusterSizeSectors1216'], formula: 'sectorSize × clusterSizeSectors' },
 | 
			
		||||
                            { id: 'numClusters1216', label: 'Anzahl Cluster', dependencies: ['partitionSizeInSectors1216', 'sectorSize1216', 'reservedSectors1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216', 'maxRootEntries1216', 'clusterSizeSectors1216'], formula: 'dataSize ÷ clusterSize' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'Spezifischer Cluster',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'clusterStart1216', label: 'Cluster Anfang (Bytes)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216', 'maxRootEntries1216', 'clusterSizeSectors1216', 'clusterNumber1216'], formula: 'dataStart + (clusterNumber - 0x2) × clusterSize' },
 | 
			
		||||
                            { id: 'clusterStartSector1216', label: 'Cluster Anfang (Sektor)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'numFATs1216', 'fatSizeSectors1216', 'maxRootEntries1216', 'clusterSizeSectors1216', 'clusterNumber1216'], formula: 'clusterStart ÷ sectorSize' },
 | 
			
		||||
                            { id: 'fatEntryPos1216', label: 'FAT-Eintrag Position (Bytes, nur FAT16)', dependencies: ['reservedSectors1216', 'sectorSize1216', 'baseOffset1216', 'clusterNumber1216'], formula: 'fatStart + (clusterNumber × 2) für FAT16' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                formulas: [
 | 
			
		||||
                    { name: 'FAT-Bereich Anfang', expression: 'Reservierte Sektoren × Sektorgröße + Basis-Offset' },
 | 
			
		||||
                    { name: 'FAT-Bereich Größe', expression: 'Anzahl FATs × FAT-Größe in Sektoren × Sektorgröße' },
 | 
			
		||||
                    { name: 'FAT2 Anfang', expression: 'FAT-Bereich Anfang + FAT-Größe in Bytes' },
 | 
			
		||||
                    { name: 'FAT-Bereich Ende', expression: 'FAT-Bereich Anfang + FAT-Bereich Größe' },
 | 
			
		||||
                    { name: 'Root-Directory Größe', expression: 'Max. Root-Einträge × 0x20' },
 | 
			
		||||
                    { name: 'Daten-Bereich Anfang', expression: 'FAT-Bereich Ende + Root-Directory Größe' },
 | 
			
		||||
                    { name: 'Daten-Bereich Größe', expression: '(Partitionsgröße × Sektorgröße) - (Daten-Bereich Anfang - Basis-Offset)' },
 | 
			
		||||
                    { name: 'Cluster-Größe', expression: 'Sektorgröße × Cluster-Größe in Sektoren' },
 | 
			
		||||
                    { name: 'Anzahl Cluster', expression: 'Daten-Bereich Größe ÷ Cluster-Größe' },
 | 
			
		||||
                    { name: 'Cluster Anfang', expression: 'Daten-Bereich Anfang + (Cluster-Nummer - 0x2) × Cluster-Größe' },
 | 
			
		||||
                    { name: 'FAT-Eintrag Position', expression: 'FAT-Bereich Anfang + (Cluster-Nummer × Eintraggröße)' }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: 'fat32',
 | 
			
		||||
                name: 'FAT32',
 | 
			
		||||
                constants: [
 | 
			
		||||
                    { id: 'baseOffset32', label: 'Basis-Offset', unit: 'Bytes', default: '0x0' },
 | 
			
		||||
                    { id: 'sectorSize32', label: 'Sektorgröße', unit: 'Bytes', default: '0x200' }
 | 
			
		||||
                ],
 | 
			
		||||
                inputs: [
 | 
			
		||||
                    { id: 'reservedSectors32', label: 'Reservierte Sektoren', unit: 'Anzahl', placeholder: '0x20' },
 | 
			
		||||
                    { id: 'numFATs32', label: 'Anzahl FATs', unit: 'Anzahl', placeholder: '0x2' },
 | 
			
		||||
                    { id: 'fatSizeSectors32', label: 'FAT-Größe', unit: 'Sektoren', placeholder: '0x20000' },
 | 
			
		||||
                    { id: 'rootDirCluster32', label: 'Root-Directory Cluster', unit: 'Nummer', placeholder: '0x2' },
 | 
			
		||||
                    { id: 'partitionSizeInSectors32', label: 'Partitionsgröße', unit: 'Sektoren', placeholder: '0x100000' },
 | 
			
		||||
                    { id: 'clusterSizeSectors32', label: 'Clustergröße', unit: 'Sektoren', placeholder: '0x8' },
 | 
			
		||||
                    { id: 'clusterNumber32', label: 'Cluster-Nummer', unit: 'Nummer', placeholder: '0x2' }
 | 
			
		||||
                ],
 | 
			
		||||
                resultGroups: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'FAT-Struktur',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'fatStart32', label: 'FAT-Bereich Anfang (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32'], formula: 'reservedSectors × sectorSize + baseOffset' },
 | 
			
		||||
                            { id: 'fatStartSector32', label: 'FAT-Bereich Anfang (Sektor)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32'], formula: 'fatStart ÷ sectorSize' },
 | 
			
		||||
                            { id: 'fatSize32', label: 'FAT-Bereich Größe (Bytes)', dependencies: ['numFATs32', 'fatSizeSectors32', 'sectorSize32'], formula: 'numFATs × fatSizeSectors × sectorSize' },
 | 
			
		||||
                            { id: 'fat2Start32', label: 'FAT2 Anfang (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'fatSizeSectors32'], formula: 'fatStart + (fatSizeSectors × sectorSize)' },
 | 
			
		||||
                            { id: 'fatEnd32', label: 'FAT-Bereich Ende (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32'], formula: 'fatStart + fatSize' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'Daten-Bereich',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'dataStart32', label: 'Daten-Bereich Anfang (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32'], formula: 'fatEnd' },
 | 
			
		||||
                            { id: 'dataStartSector32', label: 'Daten-Bereich Anfang (Sektor)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32'], formula: 'dataStart ÷ sectorSize' },
 | 
			
		||||
                            { id: 'dataSize32', label: 'Daten-Bereich Größe (Bytes)', dependencies: ['partitionSizeInSectors32', 'sectorSize32', 'reservedSectors32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32'], formula: '(partitionSizeInSectors × sectorSize) - (dataStart - baseOffset)' },
 | 
			
		||||
                            { id: 'clusterSize32', label: 'Cluster-Größe (Bytes)', dependencies: ['sectorSize32', 'clusterSizeSectors32'], formula: 'sectorSize × clusterSizeSectors' },
 | 
			
		||||
                            { id: 'numClusters32', label: 'Anzahl Cluster', dependencies: ['partitionSizeInSectors32', 'sectorSize32', 'reservedSectors32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32', 'clusterSizeSectors32'], formula: 'dataSize ÷ clusterSize' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        name: 'Spezifischer Cluster',
 | 
			
		||||
                        results: [
 | 
			
		||||
                            { id: 'clusterStart32', label: 'Cluster Anfang (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32', 'clusterSizeSectors32', 'clusterNumber32'], formula: 'dataStart + (clusterNumber - 0x2) × clusterSize' },
 | 
			
		||||
                            { id: 'clusterStartSector32', label: 'Cluster Anfang (Sektor)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32', 'clusterSizeSectors32', 'clusterNumber32'], formula: 'clusterStart ÷ sectorSize' },
 | 
			
		||||
                            { id: 'fatEntryPos32', label: 'FAT-Eintrag Position (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'clusterNumber32'], formula: 'fatStart + (clusterNumber × 4)' },
 | 
			
		||||
                            { id: 'rootDirPos32', label: 'Root-Directory Position (Bytes)', dependencies: ['reservedSectors32', 'sectorSize32', 'baseOffset32', 'numFATs32', 'fatSizeSectors32', 'clusterSizeSectors32', 'rootDirCluster32'], formula: 'dataStart + (rootDirCluster - 0x2) × clusterSize' }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                formulas: [
 | 
			
		||||
                    { name: 'FAT-Bereich Anfang', expression: 'Reservierte Sektoren × Sektorgröße + Basis-Offset' },
 | 
			
		||||
                    { name: 'FAT-Bereich Größe', expression: 'Anzahl FATs × FAT-Größe in Sektoren × Sektorgröße' },
 | 
			
		||||
                    { name: 'FAT2 Anfang', expression: 'FAT-Bereich Anfang + FAT-Größe in Bytes' },
 | 
			
		||||
                    { name: 'FAT-Bereich Ende', expression: 'FAT-Bereich Anfang + FAT-Bereich Größe' },
 | 
			
		||||
                    { name: 'Daten-Bereich Anfang', expression: 'FAT-Bereich Ende (kein festes Root Directory)' },
 | 
			
		||||
                    { name: 'Daten-Bereich Größe', expression: '(Partitionsgröße × Sektorgröße) - (Daten-Bereich Anfang - Basis-Offset)' },
 | 
			
		||||
                    { name: 'Cluster-Größe', expression: 'Sektorgröße × Cluster-Größe in Sektoren' },
 | 
			
		||||
                    { name: 'Anzahl Cluster', expression: 'Daten-Bereich Größe ÷ Cluster-Größe' },
 | 
			
		||||
                    { name: 'Cluster Anfang', expression: 'Daten-Bereich Anfang + (Cluster-Nummer - 0x2) × Cluster-Größe' },
 | 
			
		||||
                    { name: 'FAT-Eintrag Position', expression: 'FAT-Bereich Anfang + (Cluster-Nummer × 4)' },
 | 
			
		||||
                    { name: 'Root-Directory Position', expression: 'Daten-Bereich Anfang + (Root-Directory Cluster - 0x2) × Cluster-Größe' }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    calculate(variantId) {
 | 
			
		||||
        const values = this.getInputValues(variantId);
 | 
			
		||||
        const results = {};
 | 
			
		||||
 | 
			
		||||
        if (variantId === 'fat1216') {
 | 
			
		||||
            this.calculateFAT1216(values, results);
 | 
			
		||||
        } else if (variantId === 'fat32') {
 | 
			
		||||
            this.calculateFAT32(values, results);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    calculateFAT1216(values, results) {
 | 
			
		||||
        const suffix = '1216';
 | 
			
		||||
 | 
			
		||||
        // FAT Start
 | 
			
		||||
        if (checkDependencies([`reservedSectors${suffix}`, `sectorSize${suffix}`, `baseOffset${suffix}`])) {
 | 
			
		||||
            results.fatStart = values.reservedSectors1216 * values.sectorSize1216 + values.baseOffset1216;
 | 
			
		||||
            results.fatStartSector = Math.floor(results.fatStart / values.sectorSize1216);
 | 
			
		||||
            updateResultItem(`fatStart${suffix}`, results.fatStart, true, 
 | 
			
		||||
                `0x${values.reservedSectors1216.toString(16)} × 0x${values.sectorSize1216.toString(16)} + 0x${values.baseOffset1216.toString(16)} = 0x${results.fatStart.toString(16)}`);
 | 
			
		||||
            updateResultItem(`fatStartSector${suffix}`, results.fatStartSector, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} ÷ 0x${values.sectorSize1216.toString(16)} = 0x${results.fatStartSector.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatStart${suffix}`, 0, false);
 | 
			
		||||
            updateResultItem(`fatStartSector${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT Size
 | 
			
		||||
        if (checkDependencies([`numFATs${suffix}`, `fatSizeSectors${suffix}`, `sectorSize${suffix}`])) {
 | 
			
		||||
            results.fatSize = values.numFATs1216 * values.fatSizeSectors1216 * values.sectorSize1216;
 | 
			
		||||
            updateResultItem(`fatSize${suffix}`, results.fatSize, true,
 | 
			
		||||
                `0x${values.numFATs1216.toString(16)} × 0x${values.fatSizeSectors1216.toString(16)} × 0x${values.sectorSize1216.toString(16)} = 0x${results.fatSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatSize${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT2 Start
 | 
			
		||||
        if (checkDependencies([`reservedSectors${suffix}`, `sectorSize${suffix}`, `baseOffset${suffix}`, `fatSizeSectors${suffix}`]) && results.fatStart !== undefined) {
 | 
			
		||||
            results.fat2Start = results.fatStart + (values.fatSizeSectors1216 * values.sectorSize1216);
 | 
			
		||||
            updateResultItem(`fat2Start${suffix}`, results.fat2Start, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} + 0x${(values.fatSizeSectors1216 * values.sectorSize1216).toString(16)} = 0x${results.fat2Start.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fat2Start${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT End
 | 
			
		||||
        if (results.fatStart !== undefined && results.fatSize !== undefined) {
 | 
			
		||||
            results.fatEnd = results.fatStart + results.fatSize;
 | 
			
		||||
            updateResultItem(`fatEnd${suffix}`, results.fatEnd, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} + 0x${results.fatSize.toString(16)} = 0x${results.fatEnd.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatEnd${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Root Directory Size
 | 
			
		||||
        if (checkDependencies(['maxRootEntries1216'])) {
 | 
			
		||||
            results.rootDirSize = values.maxRootEntries1216 * 0x20;
 | 
			
		||||
            updateResultItem('rootDirSize1216', results.rootDirSize, true,
 | 
			
		||||
                `0x${values.maxRootEntries1216.toString(16)} × 0x20 = 0x${results.rootDirSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem('rootDirSize1216', 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Root Directory Start
 | 
			
		||||
        if (results.fatEnd !== undefined) {
 | 
			
		||||
            results.rootDirStart = results.fatEnd;
 | 
			
		||||
            updateResultItem('rootDirStart1216', results.rootDirStart, true,
 | 
			
		||||
                `0x${results.fatEnd.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem('rootDirStart1216', 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Data Start
 | 
			
		||||
        if (results.fatEnd !== undefined && results.rootDirSize !== undefined) {
 | 
			
		||||
            results.dataStart = results.fatEnd + results.rootDirSize;
 | 
			
		||||
            results.dataStartSector = Math.floor(results.dataStart / values.sectorSize1216);
 | 
			
		||||
            updateResultItem('dataStart1216', results.dataStart, true,
 | 
			
		||||
                `0x${results.fatEnd.toString(16)} + 0x${results.rootDirSize.toString(16)} = 0x${results.dataStart.toString(16)}`);
 | 
			
		||||
            updateResultItem('dataStartSector1216', results.dataStartSector, true,
 | 
			
		||||
                `0x${results.dataStart.toString(16)} ÷ 0x${values.sectorSize1216.toString(16)} = 0x${results.dataStartSector.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem('dataStart1216', 0, false);
 | 
			
		||||
            updateResultItem('dataStartSector1216', 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Data Size
 | 
			
		||||
        const requiredForDataSize = [`partitionSizeInSectors${suffix}`, `sectorSize1216`, `reservedSectors${suffix}`, `baseOffset1216`, `numFATs${suffix}`, `fatSizeSectors${suffix}`, 'maxRootEntries1216'];
 | 
			
		||||
        if (checkDependencies(requiredForDataSize) && results.dataStart !== undefined) {
 | 
			
		||||
            const partitionSizeBytes = values.partitionSizeInSectors1216 * values.sectorSize1216;
 | 
			
		||||
            results.dataSize = partitionSizeBytes - (results.dataStart - values.baseOffset1216);
 | 
			
		||||
            updateResultItem(`dataSize${suffix}`, results.dataSize, true,
 | 
			
		||||
                `(0x${values.partitionSizeInSectors1216.toString(16)} × 0x${values.sectorSize1216.toString(16)}) - (0x${results.dataStart.toString(16)} - 0x${values.baseOffset1216.toString(16)}) = 0x${results.dataSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`dataSize${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cluster Size
 | 
			
		||||
        if (checkDependencies([`sectorSize1216`, `clusterSizeSectors${suffix}`])) {
 | 
			
		||||
            results.clusterSize = values.sectorSize1216 * values.clusterSizeSectors1216;
 | 
			
		||||
            updateResultItem(`clusterSize${suffix}`, results.clusterSize, true,
 | 
			
		||||
                `0x${values.sectorSize1216.toString(16)} × 0x${values.clusterSizeSectors1216.toString(16)} = 0x${results.clusterSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`clusterSize${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Number of Clusters
 | 
			
		||||
        if (results.dataSize !== undefined && results.clusterSize !== undefined && results.clusterSize > 0) {
 | 
			
		||||
            results.numClusters = Math.floor(results.dataSize / results.clusterSize);
 | 
			
		||||
            updateResultItem(`numClusters${suffix}`, results.numClusters, true,
 | 
			
		||||
                `0x${results.dataSize.toString(16)} ÷ 0x${results.clusterSize.toString(16)} = 0x${results.numClusters.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`numClusters${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Specific Cluster Start
 | 
			
		||||
        const requiredForCluster = [`reservedSectors${suffix}`, `sectorSize1216`, `baseOffset1216`, `numFATs${suffix}`, `fatSizeSectors${suffix}`, 'maxRootEntries1216', `clusterSizeSectors${suffix}`, `clusterNumber${suffix}`];
 | 
			
		||||
        if (checkDependencies(requiredForCluster) && results.dataStart !== undefined && results.clusterSize !== undefined) {
 | 
			
		||||
            results.clusterStart = results.dataStart + (values.clusterNumber1216 - 0x2) * results.clusterSize;
 | 
			
		||||
            results.clusterStartSector = Math.floor(results.clusterStart / values.sectorSize1216);
 | 
			
		||||
            updateResultItem(`clusterStart${suffix}`, results.clusterStart, true,
 | 
			
		||||
                `0x${results.dataStart.toString(16)} + (0x${values.clusterNumber1216.toString(16)} - 0x2) × 0x${results.clusterSize.toString(16)} = 0x${results.clusterStart.toString(16)}`);
 | 
			
		||||
            updateResultItem(`clusterStartSector${suffix}`, results.clusterStartSector, true,
 | 
			
		||||
                `0x${results.clusterStart.toString(16)} ÷ 0x${values.sectorSize1216.toString(16)} = 0x${results.clusterStartSector.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`clusterStart${suffix}`, 0, false);
 | 
			
		||||
            updateResultItem(`clusterStartSector${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT Entry Position
 | 
			
		||||
        if (checkDependencies([`reservedSectors${suffix}`, `sectorSize1216`, `baseOffset1216`, `clusterNumber${suffix}`]) && results.fatStart !== undefined) {
 | 
			
		||||
            const entrySize = 2; // FAT16 uses 2 bytes per entry
 | 
			
		||||
            results.fatEntryPos = results.fatStart + (values.clusterNumber1216 * entrySize);
 | 
			
		||||
            updateResultItem(`fatEntryPos${suffix}`, results.fatEntryPos, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} + (0x${values.clusterNumber1216.toString(16)} × ${entrySize}) = 0x${results.fatEntryPos.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatEntryPos${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    calculateFAT32(values, results) {
 | 
			
		||||
        const suffix = '32';
 | 
			
		||||
 | 
			
		||||
        // FAT Start
 | 
			
		||||
        if (checkDependencies([`reservedSectors${suffix}`, `sectorSize${suffix}`, `baseOffset${suffix}`])) {
 | 
			
		||||
            results.fatStart = values.reservedSectors32 * values.sectorSize32 + values.baseOffset32;
 | 
			
		||||
            results.fatStartSector = Math.floor(results.fatStart / values.sectorSize32);
 | 
			
		||||
            updateResultItem(`fatStart${suffix}`, results.fatStart, true, 
 | 
			
		||||
                `0x${values.reservedSectors32.toString(16)} × 0x${values.sectorSize32.toString(16)} + 0x${values.baseOffset32.toString(16)} = 0x${results.fatStart.toString(16)}`);
 | 
			
		||||
            updateResultItem(`fatStartSector${suffix}`, results.fatStartSector, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} ÷ 0x${values.sectorSize32.toString(16)} = 0x${results.fatStartSector.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatStart${suffix}`, 0, false);
 | 
			
		||||
            updateResultItem(`fatStartSector${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT Size
 | 
			
		||||
        if (checkDependencies([`numFATs${suffix}`, `fatSizeSectors${suffix}`, `sectorSize${suffix}`])) {
 | 
			
		||||
            results.fatSize = values.numFATs32 * values.fatSizeSectors32 * values.sectorSize32;
 | 
			
		||||
            updateResultItem(`fatSize${suffix}`, results.fatSize, true,
 | 
			
		||||
                `0x${values.numFATs32.toString(16)} × 0x${values.fatSizeSectors32.toString(16)} × 0x${values.sectorSize32.toString(16)} = 0x${results.fatSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatSize${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT2 Start
 | 
			
		||||
        if (checkDependencies([`reservedSectors${suffix}`, `sectorSize${suffix}`, `baseOffset${suffix}`, `fatSizeSectors${suffix}`]) && results.fatStart !== undefined) {
 | 
			
		||||
            results.fat2Start = results.fatStart + (values.fatSizeSectors32 * values.sectorSize32);
 | 
			
		||||
            updateResultItem(`fat2Start${suffix}`, results.fat2Start, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} + 0x${(values.fatSizeSectors32 * values.sectorSize32).toString(16)} = 0x${results.fat2Start.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fat2Start${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT End
 | 
			
		||||
        if (results.fatStart !== undefined && results.fatSize !== undefined) {
 | 
			
		||||
            results.fatEnd = results.fatStart + results.fatSize;
 | 
			
		||||
            updateResultItem(`fatEnd${suffix}`, results.fatEnd, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} + 0x${results.fatSize.toString(16)} = 0x${results.fatEnd.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatEnd${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Data Start (FAT32 has no fixed root directory)
 | 
			
		||||
        if (results.fatEnd !== undefined) {
 | 
			
		||||
            results.dataStart = results.fatEnd;
 | 
			
		||||
            results.dataStartSector = Math.floor(results.dataStart / values.sectorSize32);
 | 
			
		||||
            updateResultItem('dataStart32', results.dataStart, true,
 | 
			
		||||
                `0x${results.fatEnd.toString(16)}`);
 | 
			
		||||
            updateResultItem('dataStartSector32', results.dataStartSector, true,
 | 
			
		||||
                `0x${results.dataStart.toString(16)} ÷ 0x${values.sectorSize32.toString(16)} = 0x${results.dataStartSector.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem('dataStart32', 0, false);
 | 
			
		||||
            updateResultItem('dataStartSector32', 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Data Size
 | 
			
		||||
        const requiredForDataSize = [`partitionSizeInSectors${suffix}`, `sectorSize32`, `reservedSectors${suffix}`, `baseOffset32`, `numFATs${suffix}`, `fatSizeSectors${suffix}`];
 | 
			
		||||
        if (checkDependencies(requiredForDataSize) && results.dataStart !== undefined) {
 | 
			
		||||
            const partitionSizeBytes = values.partitionSizeInSectors32 * values.sectorSize32;
 | 
			
		||||
            results.dataSize = partitionSizeBytes - (results.dataStart - values.baseOffset32);
 | 
			
		||||
            updateResultItem(`dataSize${suffix}`, results.dataSize, true,
 | 
			
		||||
                `(0x${values.partitionSizeInSectors32.toString(16)} × 0x${values.sectorSize32.toString(16)}) - (0x${results.dataStart.toString(16)} - 0x${values.baseOffset32.toString(16)}) = 0x${results.dataSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`dataSize${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Cluster Size
 | 
			
		||||
        if (checkDependencies([`sectorSize32`, `clusterSizeSectors${suffix}`])) {
 | 
			
		||||
            results.clusterSize = values.sectorSize32 * values.clusterSizeSectors32;
 | 
			
		||||
            updateResultItem(`clusterSize${suffix}`, results.clusterSize, true,
 | 
			
		||||
                `0x${values.sectorSize32.toString(16)} × 0x${values.clusterSizeSectors32.toString(16)} = 0x${results.clusterSize.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`clusterSize${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Number of Clusters
 | 
			
		||||
        if (results.dataSize !== undefined && results.clusterSize !== undefined && results.clusterSize > 0) {
 | 
			
		||||
            results.numClusters = Math.floor(results.dataSize / results.clusterSize);
 | 
			
		||||
            updateResultItem(`numClusters${suffix}`, results.numClusters, true,
 | 
			
		||||
                `0x${results.dataSize.toString(16)} ÷ 0x${results.clusterSize.toString(16)} = 0x${results.numClusters.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`numClusters${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Specific Cluster Start
 | 
			
		||||
        const requiredForCluster = [`reservedSectors${suffix}`, `sectorSize32`, `baseOffset32`, `numFATs${suffix}`, `fatSizeSectors${suffix}`, `clusterSizeSectors${suffix}`, `clusterNumber${suffix}`];
 | 
			
		||||
        if (checkDependencies(requiredForCluster) && results.dataStart !== undefined && results.clusterSize !== undefined) {
 | 
			
		||||
            results.clusterStart = results.dataStart + (values.clusterNumber32 - 0x2) * results.clusterSize;
 | 
			
		||||
            results.clusterStartSector = Math.floor(results.clusterStart / values.sectorSize32);
 | 
			
		||||
            updateResultItem(`clusterStart${suffix}`, results.clusterStart, true,
 | 
			
		||||
                `0x${results.dataStart.toString(16)} + (0x${values.clusterNumber32.toString(16)} - 0x2) × 0x${results.clusterSize.toString(16)} = 0x${results.clusterStart.toString(16)}`);
 | 
			
		||||
            updateResultItem(`clusterStartSector${suffix}`, results.clusterStartSector, true,
 | 
			
		||||
                `0x${results.clusterStart.toString(16)} ÷ 0x${values.sectorSize32.toString(16)} = 0x${results.clusterStartSector.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`clusterStart${suffix}`, 0, false);
 | 
			
		||||
            updateResultItem(`clusterStartSector${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // FAT Entry Position
 | 
			
		||||
        if (checkDependencies([`reservedSectors${suffix}`, `sectorSize32`, `baseOffset32`, `clusterNumber${suffix}`]) && results.fatStart !== undefined) {
 | 
			
		||||
            const entrySize = 4; // FAT32 uses 4 bytes per entry
 | 
			
		||||
            results.fatEntryPos = results.fatStart + (values.clusterNumber32 * entrySize);
 | 
			
		||||
            updateResultItem(`fatEntryPos${suffix}`, results.fatEntryPos, true,
 | 
			
		||||
                `0x${results.fatStart.toString(16)} + (0x${values.clusterNumber32.toString(16)} × ${entrySize}) = 0x${results.fatEntryPos.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem(`fatEntryPos${suffix}`, 0, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Root Directory Position (FAT32 specific)
 | 
			
		||||
        const requiredForRootPos = [`reservedSectors${suffix}`, `sectorSize32`, `baseOffset32`, `numFATs${suffix}`, `fatSizeSectors${suffix}`, `clusterSizeSectors${suffix}`, `rootDirCluster32`];
 | 
			
		||||
        if (checkDependencies(requiredForRootPos) && results.dataStart !== undefined && results.clusterSize !== undefined) {
 | 
			
		||||
            results.rootDirPos = results.dataStart + (values.rootDirCluster32 - 0x2) * results.clusterSize;
 | 
			
		||||
            updateResultItem('rootDirPos32', results.rootDirPos, true,
 | 
			
		||||
                `0x${results.dataStart.toString(16)} + (0x${values.rootDirCluster32.toString(16)} - 0x2) × 0x${results.clusterSize.toString(16)} = 0x${results.rootDirPos.toString(16)}`);
 | 
			
		||||
        } else {
 | 
			
		||||
            updateResultItem('rootDirPos32', 0, false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								webroot/js/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								webroot/js/main.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
			
		||||
// Main application file that coordinates all modules
 | 
			
		||||
 | 
			
		||||
import { setupTooltips, setupCopyButtons } from './utils.js';
 | 
			
		||||
import { Calculator } from './calculator.js';
 | 
			
		||||
import { FATFilesystem } from './filesystems/fat.js';
 | 
			
		||||
 | 
			
		||||
class FilesystemCalculator {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.filesystems = [
 | 
			
		||||
            new FATFilesystem()
 | 
			
		||||
        ];
 | 
			
		||||
        this.calculator = new Calculator();
 | 
			
		||||
        this.activeTab = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init() {
 | 
			
		||||
        this.renderTabs();
 | 
			
		||||
        this.renderContent();
 | 
			
		||||
        this.setupEventListeners();
 | 
			
		||||
        this.setupCalculator();
 | 
			
		||||
        this.setupUtilities();
 | 
			
		||||
        
 | 
			
		||||
        // Activate first tab
 | 
			
		||||
        if (this.filesystems[0] && this.filesystems[0].variants[0]) {
 | 
			
		||||
            this.switchTab(this.filesystems[0].variants[0].id);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderTabs() {
 | 
			
		||||
        const tabsContainer = document.getElementById('filesystem-tabs');
 | 
			
		||||
        const tabs = [];
 | 
			
		||||
 | 
			
		||||
        this.filesystems.forEach(filesystem => {
 | 
			
		||||
            filesystem.variants.forEach(variant => {
 | 
			
		||||
                tabs.push(`
 | 
			
		||||
                    <div class="tab" data-tab="${variant.id}">
 | 
			
		||||
                        ${variant.name}
 | 
			
		||||
                    </div>
 | 
			
		||||
                `);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        tabsContainer.innerHTML = tabs.join('');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    renderContent() {
 | 
			
		||||
        const contentContainer = document.getElementById('filesystem-content');
 | 
			
		||||
        const calculatorHTML = this.calculator.generateHTML();
 | 
			
		||||
        const contents = [];
 | 
			
		||||
 | 
			
		||||
        this.filesystems.forEach(filesystem => {
 | 
			
		||||
            filesystem.variants.forEach(variant => {
 | 
			
		||||
                contents.push(filesystem.generateTabContentHTML(variant.id, calculatorHTML));
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        contentContainer.innerHTML = contents.join('');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupEventListeners() {
 | 
			
		||||
        // Tab switching
 | 
			
		||||
        document.addEventListener('click', (e) => {
 | 
			
		||||
            if (e.target.classList.contains('tab')) {
 | 
			
		||||
                const tabId = e.target.getAttribute('data-tab');
 | 
			
		||||
                this.switchTab(tabId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Input listeners for all filesystems
 | 
			
		||||
        this.filesystems.forEach(filesystem => {
 | 
			
		||||
            filesystem.variants.forEach(variant => {
 | 
			
		||||
                filesystem.setupInputListeners(variant.id);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupCalculator() {
 | 
			
		||||
        this.calculator.setupEventListeners();
 | 
			
		||||
        
 | 
			
		||||
        // Make calculator functions globally available for onclick handlers
 | 
			
		||||
        window.calcInput = (digit) => this.calculator.input(digit);
 | 
			
		||||
        window.calcOperation = (operation) => this.calculator.operation(operation);
 | 
			
		||||
        window.calcEquals = () => this.calculator.equals();
 | 
			
		||||
        window.calcClear = () => this.calculator.clear();
 | 
			
		||||
        window.calcBackspace = () => this.calculator.backspace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupUtilities() {
 | 
			
		||||
        setupTooltips();
 | 
			
		||||
        setupCopyButtons();
 | 
			
		||||
        
 | 
			
		||||
        // Make copyToClipboard globally available for onclick handlers
 | 
			
		||||
        window.copyToClipboard = (elementId) => {
 | 
			
		||||
            import('./utils.js').then(module => {
 | 
			
		||||
                module.copyToClipboard(elementId);
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switchTab(tabId) {
 | 
			
		||||
        // Update tab appearance
 | 
			
		||||
        document.querySelectorAll('.tab').forEach(tab => {
 | 
			
		||||
            tab.classList.remove('active');
 | 
			
		||||
        });
 | 
			
		||||
        document.querySelector(`[data-tab="${tabId}"]`).classList.add('active');
 | 
			
		||||
 | 
			
		||||
        // Update content visibility
 | 
			
		||||
        document.querySelectorAll('.tab-content').forEach(content => {
 | 
			
		||||
            content.classList.remove('active');
 | 
			
		||||
        });
 | 
			
		||||
        document.getElementById(tabId).classList.add('active');
 | 
			
		||||
 | 
			
		||||
        this.activeTab = tabId;
 | 
			
		||||
 | 
			
		||||
        // Update calculator display
 | 
			
		||||
        this.calculator.updateDisplay();
 | 
			
		||||
 | 
			
		||||
        // Trigger calculation for the new tab
 | 
			
		||||
        const filesystem = this.findFilesystemForTab(tabId);
 | 
			
		||||
        if (filesystem) {
 | 
			
		||||
            filesystem.calculate(tabId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    findFilesystemForTab(tabId) {
 | 
			
		||||
        for (const filesystem of this.filesystems) {
 | 
			
		||||
            if (filesystem.variants.some(variant => variant.id === tabId)) {
 | 
			
		||||
                return filesystem;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Initialize the application when DOM is loaded
 | 
			
		||||
document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
    const app = new FilesystemCalculator();
 | 
			
		||||
    app.init();
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										166
									
								
								webroot/js/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								webroot/js/utils.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,166 @@
 | 
			
		||||
// Shared utility functions for the filesystem calculator
 | 
			
		||||
 | 
			
		||||
export function parseHex(value) {
 | 
			
		||||
    if (!value) return 0;
 | 
			
		||||
    let cleanValue = value.toString().trim();
 | 
			
		||||
    if (cleanValue.startsWith('0x') || cleanValue.startsWith('0X')) {
 | 
			
		||||
        cleanValue = cleanValue.substring(2);
 | 
			
		||||
    }
 | 
			
		||||
    const parsed = parseInt(cleanValue, 16);
 | 
			
		||||
    return isNaN(parsed) ? null : Math.max(0, parsed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function formatHex(value) {
 | 
			
		||||
    if (isNaN(value) || value < 0) {
 | 
			
		||||
        return '<span class="error">Fehler</span>';
 | 
			
		||||
    }
 | 
			
		||||
    return '0x' + Math.floor(value).toString(16).toUpperCase();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function validateInput(fieldId, value) {
 | 
			
		||||
    const parsedValue = parseHex(value);
 | 
			
		||||
    const errorElement = document.getElementById(fieldId + '-error');
 | 
			
		||||
    const inputElement = document.getElementById(fieldId);
 | 
			
		||||
    
 | 
			
		||||
    if (parsedValue === null && value.trim() !== '') {
 | 
			
		||||
        errorElement.textContent = 'Ungültiger Hex-Wert';
 | 
			
		||||
        inputElement.classList.add('error');
 | 
			
		||||
        return false;
 | 
			
		||||
    } else {
 | 
			
		||||
        errorElement.textContent = '';
 | 
			
		||||
        inputElement.classList.remove('error');
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function checkDependencies(deps) {
 | 
			
		||||
    return deps.every(dep => {
 | 
			
		||||
        const element = document.getElementById(dep);
 | 
			
		||||
        return element && element.value.trim() !== '' && validateInput(dep, element.value);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function updateResultItem(elementId, value, available, calculation = '') {
 | 
			
		||||
    const element = document.getElementById(elementId);
 | 
			
		||||
    if (!element) return;
 | 
			
		||||
    
 | 
			
		||||
    const resultItem = element.closest('.result-item');
 | 
			
		||||
    
 | 
			
		||||
    if (available) {
 | 
			
		||||
        element.innerHTML = formatHex(value);
 | 
			
		||||
        resultItem.classList.remove('unavailable');
 | 
			
		||||
        resultItem.classList.add('available');
 | 
			
		||||
        if (calculation) {
 | 
			
		||||
            element.setAttribute('data-result-formula', calculation);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        element.innerHTML = '-';
 | 
			
		||||
        resultItem.classList.remove('available');
 | 
			
		||||
        resultItem.classList.add('unavailable');
 | 
			
		||||
        element.removeAttribute('data-result-formula');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function copyToClipboard(elementId) {
 | 
			
		||||
    const element = document.getElementById(elementId);
 | 
			
		||||
    if (!element) return;
 | 
			
		||||
    
 | 
			
		||||
    const text = element.textContent.trim();
 | 
			
		||||
 | 
			
		||||
    if (text === '-' || text.includes('Fehler')) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    navigator.clipboard.writeText(text).then(() => {
 | 
			
		||||
        const btn = element.parentElement.querySelector('.copy-btn');
 | 
			
		||||
        if (btn) {
 | 
			
		||||
            const originalText = btn.textContent;
 | 
			
		||||
            btn.textContent = '✓';
 | 
			
		||||
            btn.style.color = '#88cc88';
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                btn.textContent = originalText;
 | 
			
		||||
                btn.style.color = '';
 | 
			
		||||
            }, 1000);
 | 
			
		||||
        }
 | 
			
		||||
    }).catch(err => {
 | 
			
		||||
        console.error('Fehler beim Kopieren:', err);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Tooltip functionality
 | 
			
		||||
let tooltip = null;
 | 
			
		||||
 | 
			
		||||
export function createTooltip() {
 | 
			
		||||
    if (!tooltip) {
 | 
			
		||||
        tooltip = document.createElement('div');
 | 
			
		||||
        tooltip.className = 'tooltip';
 | 
			
		||||
        tooltip.style.display = 'none';
 | 
			
		||||
        document.body.appendChild(tooltip);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function showTooltip(element, text) {
 | 
			
		||||
    createTooltip();
 | 
			
		||||
    tooltip.innerHTML = text;
 | 
			
		||||
    tooltip.style.display = 'block';
 | 
			
		||||
 | 
			
		||||
    const rect = element.getBoundingClientRect();
 | 
			
		||||
    const tooltipRect = tooltip.getBoundingClientRect();
 | 
			
		||||
 | 
			
		||||
    let left = rect.left + (rect.width / 2) - (tooltipRect.width / 2);
 | 
			
		||||
    let top = rect.top - tooltipRect.height - 8;
 | 
			
		||||
 | 
			
		||||
    if (left < 10) left = 10;
 | 
			
		||||
    if (left + tooltipRect.width > window.innerWidth - 10) {
 | 
			
		||||
        left = window.innerWidth - tooltipRect.width - 10;
 | 
			
		||||
    }
 | 
			
		||||
    if (top < 10) {
 | 
			
		||||
        top = rect.bottom + 8;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tooltip.style.left = left + 'px';
 | 
			
		||||
    tooltip.style.top = top + 'px';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hideTooltip() {
 | 
			
		||||
    if (tooltip) {
 | 
			
		||||
        tooltip.style.display = 'none';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Setup tooltip event listeners
 | 
			
		||||
export function setupTooltips() {
 | 
			
		||||
    document.addEventListener('mouseover', function (e) {
 | 
			
		||||
        if (e.target.classList.contains('result-label')) {
 | 
			
		||||
            const formula = e.target.getAttribute('data-formula');
 | 
			
		||||
            if (formula) {
 | 
			
		||||
                showTooltip(e.target, `Formel: ${formula}`);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (e.target.classList.contains('result-value')) {
 | 
			
		||||
            const formula = e.target.getAttribute('data-result-formula');
 | 
			
		||||
            if (formula && formula !== '') {
 | 
			
		||||
                showTooltip(e.target, `Berechnung: ${formula}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('mouseout', function (e) {
 | 
			
		||||
        if (e.target.classList.contains('result-label') || e.target.classList.contains('result-value')) {
 | 
			
		||||
            hideTooltip();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('scroll', hideTooltip);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Setup copy button functionality
 | 
			
		||||
export function setupCopyButtons() {
 | 
			
		||||
    document.addEventListener('click', function (e) {
 | 
			
		||||
        if (e.target.classList.contains('copy-btn')) {
 | 
			
		||||
            const resultValue = e.target.parentElement.querySelector('.result-value');
 | 
			
		||||
            if (resultValue) {
 | 
			
		||||
                copyToClipboard(resultValue.id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user