diff --git a/newtab.html b/newtab.html
index c6bc824..48f05d2 100644
--- a/newtab.html
+++ b/newtab.html
@@ -511,6 +511,7 @@
+
diff --git a/src/css/main.css b/src/css/main.css
index 4233370..8fd074f 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -1534,6 +1534,47 @@ body.show-desc .bm-desc { display: block; }
gap: 4px;
}
+/* Calculator Stationeers specifics */
+.calc-game-hint {
+ font-size: 10px;
+ color: var(--text-muted);
+ font-style: italic;
+ margin-top: -4px;
+ text-align: right;
+}
+.calc-game-details {
+ border-top: 1px solid var(--border);
+ padding-top: 6px;
+ margin-top: 4px;
+}
+.calc-game-details summary {
+ font-size: 10px;
+ color: var(--text-muted);
+ cursor: pointer;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+.calc-game-table {
+ width: 100%;
+ font-size: 11px;
+ border-collapse: collapse;
+ margin-top: 4px;
+}
+.calc-game-table th {
+ text-align: left;
+ color: var(--text-muted);
+ font-weight: 600;
+ padding: 2px 6px;
+ border-bottom: 1px solid var(--border);
+}
+.calc-game-table td {
+ padding: 2px 6px;
+ color: var(--text-secondary);
+}
+.calc-game-table tr:nth-child(even) td {
+ background: rgba(0,0,0,0.1);
+}
+
/* ============================================
TIMER WIDGET
============================================ */
diff --git a/src/js/calc-stationeers.js b/src/js/calc-stationeers.js
new file mode 100644
index 0000000..842b1f5
--- /dev/null
+++ b/src/js/calc-stationeers.js
@@ -0,0 +1,361 @@
+/* =============================================
+ HELLION NEWTAB — calc-stationeers.js
+ Stationeers Calculator Modus
+ ============================================= */
+
+(function() {
+ 'use strict';
+
+ const R = 8314.46261815324;
+ const COMBUSTION_ENERGY = 563452;
+ const HEAT_CAP_PURE_FUEL = 61.9;
+ const HEAT_CAP_DELTA = 172.615;
+ const BATTERY_CAPACITY = 50000;
+
+ const HEAT_CAPS = [
+ { gas: 'O\u2082', cp: 21.1 },
+ { gas: 'H\u2082', cp: 20.4 },
+ { gas: 'CO\u2082', cp: 28.2 },
+ { gas: 'N\u2082', cp: 20.6 },
+ { gas: 'H\u2082O', cp: 72.0 },
+ { gas: 'N\u2082O', cp: 23.0 },
+ { gas: 'Pollutant', cp: 24.8 }
+ ];
+
+ const GAS_VARS = ['P', 'V', 'n', 'T'];
+ const SUB_MODES = ['gas', 'furnace', 'solar', 'atmo'];
+ let _activeSubMode = 'gas';
+
+ 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;
+ if (opts.disabled) input.disabled = true;
+ 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 renderGas(container) {
+ const solveRow = document.createElement('div');
+ solveRow.className = 'calc-game-field';
+ const solveLabel = document.createElement('label');
+ solveLabel.textContent = t('calculator.sta.solve_for');
+ const solveSelect = document.createElement('select');
+ solveSelect.className = 'calc-game-input';
+
+ GAS_VARS.forEach(v => {
+ const opt = document.createElement('option');
+ opt.value = v;
+ opt.textContent = t('calculator.sta.var.' + v);
+ solveSelect.appendChild(opt);
+ });
+
+ solveRow.append(solveLabel, solveSelect);
+ container.appendChild(solveRow);
+
+ const fields = {};
+ const defaults = { P: 101.325, V: 1000, n: 1, T: 293.15 };
+
+ GAS_VARS.forEach(v => {
+ const f = createField(
+ 'calculator.sta.var.' + v + '_label',
+ defaults[v],
+ { step: 'any' }
+ );
+ fields[v] = f;
+ container.appendChild(f.row);
+ });
+
+ const tempHelper = document.createElement('div');
+ tempHelper.className = 'calc-game-hint';
+ container.appendChild(tempHelper);
+
+ const resultOutput = createOutput('calculator.sta.result');
+ container.appendChild(resultOutput.row);
+
+ function calc() {
+ const solveFor = solveSelect.value;
+
+ GAS_VARS.forEach(v => {
+ fields[v].input.disabled = (v === solveFor);
+ fields[v].input.style.opacity = (v === solveFor) ? '0.5' : '1';
+ });
+
+ const P_kPa = parseFloat(fields.P.input.value) || 0;
+ const P = P_kPa * 1000;
+ const V = parseFloat(fields.V.input.value) || 0;
+ const n = parseFloat(fields.n.input.value) || 0;
+ const T = parseFloat(fields.T.input.value) || 0;
+
+ let result = null;
+ let unit = '';
+
+ switch (solveFor) {
+ case 'P':
+ if (V > 0) { result = (n * R * T) / V; result /= 1000; unit = 'kPa'; }
+ break;
+ case 'V':
+ if (P > 0) { result = (n * R * T) / P; unit = 'L'; }
+ break;
+ case 'n':
+ if (R * T > 0) { result = (P * V) / (R * T); unit = 'mol'; }
+ break;
+ case 'T':
+ if (n * R > 0) { result = (P * V) / (n * R); unit = 'K'; }
+ break;
+ }
+
+ if (result !== null && isFinite(result)) {
+ fields[solveFor].input.value = Calculator._formatResult(result);
+ resultOutput.value.textContent = Calculator._formatResult(result) + ' ' + unit;
+ } else {
+ resultOutput.value.textContent = '-';
+ }
+
+ const tempVal = parseFloat(fields.T.input.value) || 0;
+ tempHelper.textContent = Calculator._formatResult(tempVal - 273.15) + ' \u00B0C';
+ }
+
+ GAS_VARS.forEach(v => {
+ fields[v].input.addEventListener('input', calc);
+ });
+ solveSelect.addEventListener('change', calc);
+ calc();
+ }
+
+ function renderFurnace(container) {
+ const fuelField = createField('calculator.sta.fuel_ratio', 0.5, { step: 0.01, min: 0, max: 1 });
+ const tempField = createField('calculator.sta.start_temp', 293.15, { step: 1, min: 0 });
+ const pressField = createField('calculator.sta.start_pressure', 101.325, { step: 0.1, min: 0 });
+
+ const tempOutput = createOutput('calculator.sta.temp_after');
+ const pressOutput = createOutput('calculator.sta.pressure_after');
+ const warningEl = document.createElement('div');
+ warningEl.className = 'calc-game-warning';
+
+ function calc() {
+ const fuel = parseFloat(fuelField.input.value) || 0;
+ const T_vor = parseFloat(tempField.input.value) || 293.15;
+ const P_vor = parseFloat(pressField.input.value) || 101.325;
+
+ warningEl.textContent = '';
+ if (fuel < 0.05) {
+ warningEl.textContent = t('calculator.sta.warn_low_fuel');
+ }
+ if (P_vor < 10) {
+ warningEl.textContent += (warningEl.textContent ? ' ' : '') + t('calculator.sta.warn_low_pressure');
+ }
+
+ const specificHeat = HEAT_CAP_PURE_FUEL;
+ const T_nach = (T_vor * specificHeat + fuel * COMBUSTION_ENERGY) / (specificHeat + fuel * HEAT_CAP_DELTA);
+ const P_nach = P_vor * T_nach * (1 + 5.7 * fuel) / T_vor;
+
+ tempOutput.value.textContent = Calculator._formatResult(T_nach) + ' K (' + Calculator._formatResult(T_nach - 273.15) + ' \u00B0C)';
+ pressOutput.value.textContent = Calculator._formatResult(P_nach) + ' kPa';
+ }
+
+ [fuelField, tempField, pressField].forEach(f => f.input.addEventListener('input', calc));
+
+ container.append(fuelField.row, tempField.row, pressField.row, warningEl, tempOutput.row, pressOutput.row);
+ calc();
+ }
+
+ function renderSolar(container) {
+ const panelField = createField('calculator.sta.panels', 12, { step: 1, min: 1 });
+ const wattField = createField('calculator.sta.watts_per_panel', 500, { step: 10, min: 1 });
+ const dayField = createField('calculator.sta.day_length', 600, { step: 1, min: 1 });
+ const nightField = createField('calculator.sta.night_length', 600, { step: 1, min: 1 });
+ const consumeField = createField('calculator.sta.consumption', 2000, { step: 10, min: 0 });
+
+ const genOutput = createOutput('calculator.sta.generation');
+ const surplusOutput = createOutput('calculator.sta.surplus');
+ const nightOutput = createOutput('calculator.sta.night_energy');
+ const battOutput = createOutput('calculator.sta.batteries_needed');
+
+ function calc() {
+ const panels = parseFloat(panelField.input.value) || 0;
+ const wpp = parseFloat(wattField.input.value) || 0;
+ const nightLen = parseFloat(nightField.input.value) || 0;
+ const consume = parseFloat(consumeField.input.value) || 0;
+
+ const generation = panels * wpp;
+ const surplus = generation - consume;
+ const nightEnergy = consume * nightLen;
+ const batteries = nightEnergy > 0 ? Math.ceil(nightEnergy / BATTERY_CAPACITY) : 0;
+
+ genOutput.value.textContent = Calculator._formatResult(generation) + ' W';
+
+ surplusOutput.value.textContent = Calculator._formatResult(surplus) + ' W';
+ if (surplus < 0) {
+ surplusOutput.value.style.color = 'var(--danger)';
+ } else {
+ surplusOutput.value.style.color = '';
+ }
+
+ nightOutput.value.textContent = Calculator._formatResult(nightEnergy) + ' Ws';
+ battOutput.value.textContent = batteries;
+ }
+
+ [panelField, wattField, dayField, nightField, consumeField].forEach(f => f.input.addEventListener('input', calc));
+
+ container.append(panelField.row, wattField.row, dayField.row, nightField.row, consumeField.row,
+ genOutput.row, surplusOutput.row, nightOutput.row, battOutput.row);
+ calc();
+ }
+
+ function renderAtmo(container) {
+ const targetField = createField('calculator.sta.target_temp', 293.15, { step: 1 });
+ const gas1Field = createField('calculator.sta.gas1_temp', 200, { step: 1 });
+ const gas2Field = createField('calculator.sta.gas2_temp', 400, { step: 1 });
+
+ const m1Output = createOutput('calculator.sta.mixer_input1');
+ const m2Output = createOutput('calculator.sta.mixer_input2');
+
+ function calc() {
+ const T0 = parseFloat(targetField.input.value) || 0;
+ const T1 = parseFloat(gas1Field.input.value) || 0;
+ const T2 = parseFloat(gas2Field.input.value) || 0;
+
+ const denom = Math.abs(T1 - T0) + Math.abs(T2 - T0);
+ if (denom === 0) {
+ m1Output.value.textContent = '50%';
+ m2Output.value.textContent = '50%';
+ return;
+ }
+
+ const M1 = Math.abs(T2 - T0) / denom;
+ const M2 = 1 - M1;
+
+ m1Output.value.textContent = Calculator._formatResult(M1 * 100) + '%';
+ m2Output.value.textContent = Calculator._formatResult(M2 * 100) + '%';
+ }
+
+ [targetField, gas1Field, gas2Field].forEach(f => f.input.addEventListener('input', calc));
+
+ container.append(targetField.row, gas1Field.row, gas2Field.row, m1Output.row, m2Output.row);
+ calc();
+
+ const details = document.createElement('details');
+ details.className = 'calc-game-details';
+
+ const summary = document.createElement('summary');
+ summary.textContent = t('calculator.sta.heat_cap_ref');
+ details.appendChild(summary);
+
+ const table = document.createElement('table');
+ table.className = 'calc-game-table';
+
+ const thead = document.createElement('thead');
+ const headerRow = document.createElement('tr');
+ const thGas = document.createElement('th');
+ thGas.textContent = t('calculator.sta.gas');
+ const thCp = document.createElement('th');
+ thCp.textContent = 'Cp (J/mol\u00B7K)';
+ headerRow.append(thGas, thCp);
+ thead.appendChild(headerRow);
+
+ const tbody = document.createElement('tbody');
+ HEAT_CAPS.forEach(entry => {
+ const tr = document.createElement('tr');
+ const tdGas = document.createElement('td');
+ tdGas.textContent = entry.gas;
+ const tdCp = document.createElement('td');
+ tdCp.textContent = entry.cp;
+ tr.append(tdGas, tdCp);
+ tbody.appendChild(tr);
+ });
+
+ table.append(thead, tbody);
+ details.appendChild(table);
+ container.appendChild(details);
+ }
+
+ async function loadState() {
+ const data = await Store.get(Calculator.STORAGE_KEY);
+ if (data && data.calculator && data.calculator.stationeers) {
+ const s = data.calculator.stationeers;
+ 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.stationeers = { lastSubMode: _activeSubMode };
+ await Store.set(Calculator.STORAGE_KEY, data);
+ }
+
+ function renderSubMode(container) {
+ container.textContent = '';
+ switch (_activeSubMode) {
+ case 'gas': renderGas(container); break;
+ case 'furnace': renderFurnace(container); break;
+ case 'solar': renderSolar(container); break;
+ case 'atmo': renderAtmo(container); break;
+ }
+ }
+
+ Calculator.registerMode('stationeers', {
+ label: '\uD83D\uDE80',
+ shortName: 'STA',
+ titleKey: 'calculator.tab.stationeers',
+
+ 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.sta.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 ca0722d..6a213a0 100644
--- a/src/js/i18n.js
+++ b/src/js/i18n.js
@@ -148,6 +148,46 @@ const STRINGS = {
'calculator.fac.belt_needed': 'Belt benötigt',
'calculator.fac.exceeds_belt': 'Übersteigt max. Belt',
+ // Stationeers Calculator
+ 'calculator.tab.stationeers': 'Stationeers',
+ 'calculator.sta.tab.gas': 'Gas',
+ 'calculator.sta.tab.furnace': 'Ofen',
+ 'calculator.sta.tab.solar': 'Solar',
+ 'calculator.sta.tab.atmo': 'Atmo',
+ 'calculator.sta.solve_for': 'Gesucht',
+ 'calculator.sta.var.P': 'Druck (P)',
+ 'calculator.sta.var.V': 'Volumen (V)',
+ 'calculator.sta.var.n': 'Stoffmenge (n)',
+ 'calculator.sta.var.T': 'Temperatur (T)',
+ 'calculator.sta.var.P_label': 'Druck (kPa)',
+ 'calculator.sta.var.V_label': 'Volumen (L)',
+ 'calculator.sta.var.n_label': 'Stoffmenge (mol)',
+ 'calculator.sta.var.T_label': 'Temperatur (K)',
+ 'calculator.sta.result': 'Ergebnis',
+ 'calculator.sta.fuel_ratio': 'Fuel-Anteil (0-1)',
+ 'calculator.sta.start_temp': 'Start-Temperatur (K)',
+ 'calculator.sta.start_pressure': 'Start-Druck (kPa)',
+ 'calculator.sta.temp_after': 'T nach Zündung',
+ 'calculator.sta.pressure_after': 'P nach Zündung',
+ 'calculator.sta.warn_low_fuel': '\u26A0 Fuel unter 5%',
+ 'calculator.sta.warn_low_pressure': '\u26A0 Druck unter 10 kPa',
+ 'calculator.sta.panels': 'Anzahl Panels',
+ 'calculator.sta.watts_per_panel': 'Watt/Panel',
+ 'calculator.sta.day_length': 'Taglänge (s)',
+ 'calculator.sta.night_length': 'Nachtlänge (s)',
+ 'calculator.sta.consumption': 'Verbrauch (W)',
+ 'calculator.sta.generation': 'Erzeugung',
+ 'calculator.sta.surplus': 'Überschuss',
+ 'calculator.sta.night_energy': 'Nacht-Energie',
+ 'calculator.sta.batteries_needed': 'Batterien benötigt',
+ 'calculator.sta.target_temp': 'Ziel-Temperatur (K)',
+ 'calculator.sta.gas1_temp': 'Gas 1 Temperatur (K)',
+ 'calculator.sta.gas2_temp': 'Gas 2 Temperatur (K)',
+ 'calculator.sta.mixer_input1': 'Mixer Input 1',
+ 'calculator.sta.mixer_input2': 'Mixer Input 2',
+ 'calculator.sta.heat_cap_ref': 'Wärmekapazitäten (Referenz)',
+ 'calculator.sta.gas': 'Gas',
+
// Timer
'timer.title': 'Timer',
'timer.start': 'Start',
@@ -520,6 +560,46 @@ const STRINGS = {
'calculator.fac.belt_needed': 'Belt needed',
'calculator.fac.exceeds_belt': 'Exceeds max belt',
+ // Stationeers Calculator
+ 'calculator.tab.stationeers': 'Stationeers',
+ 'calculator.sta.tab.gas': 'Gas',
+ 'calculator.sta.tab.furnace': 'Furnace',
+ 'calculator.sta.tab.solar': 'Solar',
+ 'calculator.sta.tab.atmo': 'Atmo',
+ 'calculator.sta.solve_for': 'Solve for',
+ 'calculator.sta.var.P': 'Pressure (P)',
+ 'calculator.sta.var.V': 'Volume (V)',
+ 'calculator.sta.var.n': 'Amount (n)',
+ 'calculator.sta.var.T': 'Temperature (T)',
+ 'calculator.sta.var.P_label': 'Pressure (kPa)',
+ 'calculator.sta.var.V_label': 'Volume (L)',
+ 'calculator.sta.var.n_label': 'Amount (mol)',
+ 'calculator.sta.var.T_label': 'Temperature (K)',
+ 'calculator.sta.result': 'Result',
+ 'calculator.sta.fuel_ratio': 'Fuel Ratio (0-1)',
+ 'calculator.sta.start_temp': 'Start Temperature (K)',
+ 'calculator.sta.start_pressure': 'Start Pressure (kPa)',
+ 'calculator.sta.temp_after': 'T after ignition',
+ 'calculator.sta.pressure_after': 'P after ignition',
+ 'calculator.sta.warn_low_fuel': '\u26A0 Fuel below 5%',
+ 'calculator.sta.warn_low_pressure': '\u26A0 Pressure below 10 kPa',
+ 'calculator.sta.panels': 'Panel Count',
+ 'calculator.sta.watts_per_panel': 'Watts/Panel',
+ 'calculator.sta.day_length': 'Day Length (s)',
+ 'calculator.sta.night_length': 'Night Length (s)',
+ 'calculator.sta.consumption': 'Consumption (W)',
+ 'calculator.sta.generation': 'Generation',
+ 'calculator.sta.surplus': 'Surplus',
+ 'calculator.sta.night_energy': 'Night Energy',
+ 'calculator.sta.batteries_needed': 'Batteries needed',
+ 'calculator.sta.target_temp': 'Target Temperature (K)',
+ 'calculator.sta.gas1_temp': 'Gas 1 Temperature (K)',
+ 'calculator.sta.gas2_temp': 'Gas 2 Temperature (K)',
+ 'calculator.sta.mixer_input1': 'Mixer Input 1',
+ 'calculator.sta.mixer_input2': 'Mixer Input 2',
+ 'calculator.sta.heat_cap_ref': 'Heat Capacities (Reference)',
+ 'calculator.sta.gas': 'Gas',
+
// Timer
'timer.title': 'Timer',
'timer.start': 'Start',