diff --git a/newtab.html b/newtab.html index aa1eac3..dd0bd02 100644 --- a/newtab.html +++ b/newtab.html @@ -509,6 +509,7 @@ + diff --git a/src/css/main.css b/src/css/main.css index bc0c866..4233370 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -1452,6 +1452,88 @@ body.show-desc .bm-desc { display: block; } border-top: 1px solid var(--border); } +/* Calculator Game Modes (shared) */ +.calc-game-subtabs { + display: flex; + gap: 4px; + margin-bottom: 8px; +} +.calc-game-subtab { + flex: 1; + padding: 5px 4px; + background: rgba(255,255,255,0.04); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-muted); + font-size: 10px; + font-family: 'Rajdhani', sans-serif; + cursor: pointer; + text-transform: uppercase; + letter-spacing: 0.5px; + transition: all 0.15s; +} +.calc-game-subtab:hover { + color: var(--text-secondary); +} +.calc-game-subtab.active { + background: var(--accent-dim); + border-color: var(--accent); + color: var(--accent); + font-weight: 600; +} +.calc-game-field { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} +.calc-game-field label { + font-size: 11px; + color: var(--text-secondary); + min-width: 90px; + flex-shrink: 0; +} +.calc-game-input { + flex: 1; + padding: 5px 8px; + background: rgba(0,0,0,0.3); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + color: var(--text-primary); + font-size: 13px; + font-family: 'Rajdhani', monospace; +} +.calc-game-output { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 8px; + background: rgba(0,0,0,0.2); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + margin-top: 4px; +} +.calc-game-output span:first-child { + font-size: 11px; + color: var(--text-muted); +} +.calc-game-value { + font-size: 14px; + color: var(--accent); + font-weight: 600; + font-family: 'Rajdhani', monospace; +} +.calc-game-warning { + font-size: 10px; + color: var(--danger); + padding: 2px 0; +} +.calc-game-content { + display: flex; + flex-direction: column; + gap: 4px; +} + /* ============================================ TIMER WIDGET ============================================ */ diff --git a/src/js/calc-satisfactory.js b/src/js/calc-satisfactory.js new file mode 100644 index 0000000..9eb60ae --- /dev/null +++ b/src/js/calc-satisfactory.js @@ -0,0 +1,184 @@ +/* ============================================= + HELLION NEWTAB — calc-satisfactory.js + Satisfactory Calculator Modus + ============================================= */ + +(function() { + 'use strict'; + + const POWER_EXPONENT = 1.321928; + const SUB_MODES = ['itemsPerMin', 'power', 'machines']; + let _activeSubMode = 'itemsPerMin'; + + function createField(labelKey, defaultVal, opts) { + opts = opts || {}; + const row = document.createElement('div'); + row.className = 'calc-game-field'; + const label = document.createElement('label'); + label.textContent = t(labelKey); + const input = document.createElement('input'); + input.type = 'number'; + input.className = 'calc-game-input'; + input.value = defaultVal; + if (opts.step) input.step = opts.step; + if (opts.min !== undefined) input.min = opts.min; + if (opts.max !== undefined) input.max = opts.max; + row.append(label, input); + return { row, input }; + } + + function createOutput(labelKey) { + const row = document.createElement('div'); + row.className = 'calc-game-output'; + const label = document.createElement('span'); + label.textContent = t(labelKey); + const value = document.createElement('span'); + value.className = 'calc-game-value'; + row.append(label, value); + return { row, value }; + } + + function renderItemsPerMin(container) { + const itemsField = createField('calculator.sat.items_per_craft', 1, { step: 1, min: 1 }); + const timeField = createField('calculator.sat.craft_time', 4, { step: 0.1, min: 0.1 }); + const clockField = createField('calculator.sat.clock_speed', 100, { step: 1, min: 1, max: 250 }); + const output = createOutput('calculator.sat.output_per_min'); + + function calc() { + const items = parseFloat(itemsField.input.value) || 0; + const time = parseFloat(timeField.input.value) || 1; + const clock = parseFloat(clockField.input.value) || 100; + const result = (items * 60) / time * (clock / 100); + output.value.textContent = Calculator._formatResult(result) + ' items/min'; + } + + [itemsField, timeField, clockField].forEach(f => f.input.addEventListener('input', calc)); + container.append(itemsField.row, timeField.row, clockField.row, output.row); + calc(); + } + + function renderPower(container) { + const basePowerField = createField('calculator.sat.base_power', 30, { step: 1, min: 0.1 }); + const clockField = createField('calculator.sat.clock_speed', 100, { step: 1, min: 1, max: 250 }); + const powerOutput = createOutput('calculator.sat.power_usage'); + const effOutput = createOutput('calculator.sat.efficiency'); + + function calc() { + const basePower = parseFloat(basePowerField.input.value) || 0; + const clock = parseFloat(clockField.input.value) || 100; + const ratio = clock / 100; + const power = basePower * Math.pow(ratio, POWER_EXPONENT); + const effPerItem = Math.pow(ratio, POWER_EXPONENT - 1); + + powerOutput.value.textContent = Calculator._formatResult(power) + ' MW'; + + if (clock > 100) { + const overhead = (effPerItem - 1) * 100; + effOutput.value.textContent = '+' + Calculator._formatResult(overhead) + '% ' + t('calculator.sat.per_item'); + effOutput.row.style.display = ''; + } else { + effOutput.row.style.display = 'none'; + } + } + + [basePowerField, clockField].forEach(f => f.input.addEventListener('input', calc)); + container.append(basePowerField.row, clockField.row, powerOutput.row, effOutput.row); + calc(); + } + + function renderMachines(container) { + const targetField = createField('calculator.sat.target_output', 60, { step: 1, min: 1 }); + const itemsField = createField('calculator.sat.items_per_craft', 1, { step: 1, min: 1 }); + const timeField = createField('calculator.sat.craft_time', 4, { step: 0.1, min: 0.1 }); + const clockField = createField('calculator.sat.clock_speed', 100, { step: 1, min: 1, max: 250 }); + const basePowerField = createField('calculator.sat.base_power', 30, { step: 1, min: 0.1 }); + const machinesOutput = createOutput('calculator.sat.machines_needed'); + const totalPowerOutput = createOutput('calculator.sat.total_power'); + + function calc() { + const target = parseFloat(targetField.input.value) || 0; + const items = parseFloat(itemsField.input.value) || 1; + const time = parseFloat(timeField.input.value) || 1; + const clock = parseFloat(clockField.input.value) || 100; + const basePower = parseFloat(basePowerField.input.value) || 0; + const ratio = clock / 100; + const itemsPerMin = (items * 60) / time * ratio; + const machines = itemsPerMin > 0 ? Math.ceil(target / itemsPerMin) : 0; + const totalPower = machines * basePower * Math.pow(ratio, POWER_EXPONENT); + machinesOutput.value.textContent = machines; + totalPowerOutput.value.textContent = Calculator._formatResult(totalPower) + ' MW'; + } + + [targetField, itemsField, timeField, clockField, basePowerField].forEach(f => f.input.addEventListener('input', calc)); + container.append(targetField.row, itemsField.row, timeField.row, clockField.row, basePowerField.row, machinesOutput.row, totalPowerOutput.row); + calc(); + } + + async function loadState() { + const data = await Store.get(Calculator.STORAGE_KEY); + if (data && data.calculator && data.calculator.satisfactory) { + const s = data.calculator.satisfactory; + if (s.lastSubMode && SUB_MODES.includes(s.lastSubMode)) _activeSubMode = s.lastSubMode; + } + } + + async function saveState() { + const data = await Store.get(Calculator.STORAGE_KEY) || {}; + if (!data.calculator) data.calculator = {}; + data.calculator.satisfactory = { lastSubMode: _activeSubMode }; + await Store.set(Calculator.STORAGE_KEY, data); + } + + function renderSubMode(container) { + container.textContent = ''; + switch (_activeSubMode) { + case 'itemsPerMin': renderItemsPerMin(container); break; + case 'power': renderPower(container); break; + case 'machines': renderMachines(container); break; + } + } + + Calculator.registerMode('satisfactory', { + label: '⚙️', + shortName: 'SAT', + titleKey: 'calculator.tab.satisfactory', + + render(bodyEl) { + bodyEl.style.padding = '8px'; + bodyEl.style.display = 'flex'; + bodyEl.style.flexDirection = 'column'; + bodyEl.style.gap = '8px'; + + loadState().then(() => { + const subContent = document.createElement('div'); + subContent.className = 'calc-game-content'; + + const bar = document.createElement('div'); + bar.className = 'calc-game-subtabs'; + + SUB_MODES.forEach(mode => { + const btn = document.createElement('button'); + btn.type = 'button'; + btn.className = 'calc-game-subtab' + (mode === _activeSubMode ? ' active' : ''); + btn.textContent = t('calculator.sat.tab.' + mode); + btn.dataset.mode = mode; + btn.addEventListener('click', () => { + bar.querySelectorAll('.calc-game-subtab').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + _activeSubMode = mode; + renderSubMode(subContent); + saveState(); + }); + bar.appendChild(btn); + }); + + bodyEl.append(bar, subContent); + renderSubMode(subContent); + }); + }, + + destroy() { + saveState(); + } + }); +})(); diff --git a/src/js/i18n.js b/src/js/i18n.js index 59a1e13..5bfc326 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -107,6 +107,21 @@ const STRINGS = { 'calculator.conv.cat.volume': 'Volumen', 'calculator.conv.cat.speed': 'Geschwindigkeit', 'calculator.conv.cat.area': 'Fläche', + 'calculator.tab.satisfactory': 'Satisfactory', + 'calculator.sat.tab.itemsPerMin': 'Items/Min', + 'calculator.sat.tab.power': 'Strom', + 'calculator.sat.tab.machines': 'Maschinen', + 'calculator.sat.items_per_craft': 'Items/Craft', + 'calculator.sat.craft_time': 'Craftzeit (s)', + 'calculator.sat.clock_speed': 'Taktrate (%)', + 'calculator.sat.base_power': 'Grundleistung (MW)', + 'calculator.sat.target_output': 'Ziel Output/Min', + 'calculator.sat.output_per_min': 'Output', + 'calculator.sat.power_usage': 'Stromverbrauch', + 'calculator.sat.efficiency': 'Effizienz', + 'calculator.sat.per_item': 'pro Item', + 'calculator.sat.machines_needed': 'Maschinen benötigt', + 'calculator.sat.total_power': 'Gesamtleistung', // Timer 'timer.title': 'Timer', @@ -439,6 +454,21 @@ const STRINGS = { 'calculator.conv.cat.volume': 'Volume', 'calculator.conv.cat.speed': 'Speed', 'calculator.conv.cat.area': 'Area', + 'calculator.tab.satisfactory': 'Satisfactory', + 'calculator.sat.tab.itemsPerMin': 'Items/Min', + 'calculator.sat.tab.power': 'Power', + 'calculator.sat.tab.machines': 'Machines', + 'calculator.sat.items_per_craft': 'Items/Craft', + 'calculator.sat.craft_time': 'Craft Time (s)', + 'calculator.sat.clock_speed': 'Clock Speed (%)', + 'calculator.sat.base_power': 'Base Power (MW)', + 'calculator.sat.target_output': 'Target Output/Min', + 'calculator.sat.output_per_min': 'Output', + 'calculator.sat.power_usage': 'Power Usage', + 'calculator.sat.efficiency': 'Efficiency', + 'calculator.sat.per_item': 'per item', + 'calculator.sat.machines_needed': 'Machines needed', + 'calculator.sat.total_power': 'Total Power', // Timer 'timer.title': 'Timer',