diff --git a/static/filterWeapons.js b/static/filterWeapons.js index 03d469f..4658696 100644 --- a/static/filterWeapons.js +++ b/static/filterWeapons.js @@ -1,644 +1,102 @@ -let selectedWeapon = null; -let selectedOperative = null; -let weapons = []; -let globalModifiers = []; -let selectedGunTypes = []; -let headshotMultiplier = 0; -let headshotBaseApplied = false; -let ignoreHeadshots = false; -let isEditing = false; +const form = document.getElementById('af-calculator'); +const outputs = { + shotDamage: document.querySelector('[data-output="shotDamage"]'), + burstDps: document.querySelector('[data-output="burstDps"]'), + sustainedDps: document.querySelector('[data-output="sustainedDps"]'), + timeToEmpty: document.querySelector('[data-output="timeToEmpty"]'), + magDamage: document.querySelector('[data-output="magDamage"]'), + ttk: document.querySelector('[data-output="ttk"]') +}; -//fetch operatives data from JSON -function fetchOperativesData() { - fetch('static/json/operative.json') - .then(response => response.json()) - .then(data => { - populateOperatives(data.operatives); - }) - .catch(error => console.error('Error fetching operatives data:', error)); -} +const resetButton = document.getElementById('af-reset'); +const copyButton = document.getElementById('af-copy'); -//populate the select element with operatives -function populateOperatives(operatives) { - const select = document.getElementById('operative-select'); - select.innerHTML = ""; - const placeholderOption = document.createElement('option'); - placeholderOption.value = ""; // Empty value for the placeholder - placeholderOption.text = "Select an operative"; - select.appendChild(placeholderOption); - operatives.forEach(operative => { - const option = document.createElement('option'); - option.value = operative.id; - option.text = operative.name; - select.appendChild(option); - }); - select.addEventListener('change', (event) => { - const selectedOperativeId = event.target.value; - if (!selectedOperativeId) { - document.getElementById('operative-modifiers').innerHTML = ''; - return; - } - selectedOperative = operatives.find(op => op.id === selectedOperativeId); - if (selectedOperative) { - updateOperativeBuffs(selectedOperative); - } - }); -} +const numberOrNull = (value) => { + const parsed = Number.parseFloat(value); + return Number.isFinite(parsed) ? parsed : null; +}; -function updateOperativeBuffs(operative) { - const operativeModifiers = operative.modifiers || []; - const operativeSection = document.getElementById('operative-modifiers'); - operativeSection.innerHTML = ''; - if (operativeModifiers.length > 0) { - operativeModifiers.forEach(mod => { - const label = document.createElement('label'); - const input = document.createElement('input'); - input.type = 'checkbox'; - input.id = mod.id; - input.value = mod.value; - input.dataset.type = mod.type; - input.dataset.buffs = mod.buffs ? JSON.stringify(mod.buffs) : ''; - input.checked = false; - label.appendChild(input); - label.appendChild(document.createTextNode(mod.label)); - operativeSection.appendChild(label); - operativeSection.appendChild(document.createElement('br')); - }); +const formatValue = (value, suffix = '') => { + if (value === null) { + return '--'; } - operativeSection.addEventListener('change', applyModifiers); - applyModifiers(); -} + const formatted = Number.isFinite(value) ? value.toFixed(2) : '--'; + return `${formatted}${suffix}`; +}; - -//fetch weapons data from JSON -function fetchWeaponsData() { - fetch('static/json/data.json') - .then(response => response.json()) - .then(data => { - weapons = data; - populateWeapons(); - createGunTypeCheckboxes(); - }) - .catch(error => console.error('Error fetching weapons data:', error)); -} - -//populate the weapon dropdown -function populateWeapons() { - const select = document.getElementById('weapon-select'); - select.innerHTML = ""; - const placeholderOption = document.createElement('option'); - placeholderOption.value = ""; - placeholderOption.text = "Select a Weapon"; - select.appendChild(placeholderOption); - weapons.forEach(weapon => { - const option = document.createElement('option'); - option.value = weapon.id; - option.text = weapon.name; - select.appendChild(option); - }); - select.addEventListener('change', (event) => { - const id = parseInt(event.target.value, 10); - if (!id) { - document.getElementById('weapon-stats').innerHTML = ''; - editBtn.disabled = true; - return; - } - selectedWeapon = weapons.find(w => w.id === id); - if (selectedWeapon) { - updateWeaponStats(selectedWeapon); - applyModifiers(); - editBtn.disabled = false; - } - }); -} - -//display the selected weapon's stats in table -function updateWeaponStats(weapon) { - const statsElement = document.getElementById('weapon-stats'); - statsElement.innerHTML = ` - - - - - - - - - - - - - - - - - - - - - -
Name - - Damage - - Per-bullet (or per-pellet) damage after the weapon’s internal multiplier is applied. - - RPMMag SizeReload Time - - DPS - - Damage per second while continuously firing. Does not include reload downtime. - - - - DPM - - Damage per minute including reload time (sustained output). - - - - Average DPS - - Sustained DPS including reload time and magazine downtime (DPM ÷ 60). - -
${weapon.name}${weapon.mag_size}
-`; - const damage = weapon.damage * weapon.damage_multiplier; - const rpm = weapon.rpm; - const reloadTime = weapon.reload_time; - document.getElementById('damage-value').innerText = damage.toFixed(2); - document.getElementById('rpm-value').innerText = rpm.toFixed(2); - document.getElementById('reload-value').innerText = reloadTime.toFixed(2) + ' seconds'; - calculateDPSAndDPM(damage, rpm, weapon.mag_size, reloadTime); -} - -//calculate DPS, DPM, and Average DPS, and update the values in the table -function calculateDPSAndDPM(damage, rpm, magSize, reloadTime) { - const rps = rpm / 60; - const dps = damage * rps; - const reloadTimeMin = reloadTime / 60; - const dpm = (magSize * damage) / (reloadTimeMin + (magSize / rpm)); - const averageDPS = dpm / 60; - document.getElementById('dps-value').innerText = dps.toFixed(2); - document.getElementById('dpm-value').innerText = dpm.toFixed(2); - document.getElementById('average-dps-value').innerText = averageDPS.toFixed(2); - const announcer = document.getElementById("stats-announcer"); - if (announcer) { - announcer.textContent = `Stats updated. DPS ${dps.toFixed(2)}. Average DPS ${averageDPS.toFixed(2)}. Reload time ${reloadTime.toFixed(2)} seconds.`; - } -} - -//fetch and populate modifiers from the JSON file -function fetchModifiers() { - fetch('static/json/modifiers.json') - .then(response => response.json()) - .then(data => { - globalModifiers = data; - populateModifiers(data); - }) - .catch(error => console.error('Error fetching modifiers:', error)); -} - -//populate modifiers into the HTML -function populateModifiers(modifiers) { - const modifiersContainer = document.getElementById('modifiers-container'); - modifiersContainer.innerHTML = ''; - const createModifierSection = (title, modifierList, sectionId) => { - const section = document.createElement('div'); - section.id = sectionId; - const header = document.createElement('h3'); - header.textContent = title; - section.appendChild(header); - modifierList.forEach(mod => { - const label = document.createElement('label'); - const input = document.createElement('input'); - input.type = 'checkbox'; - input.id = mod.id; - if (mod.buffs) { - input.dataset.buffs = JSON.stringify(mod.buffs); - } else { - input.value = mod.value; - input.dataset.type = mod.type; - } - if (mod.operatives) { - input.dataset.operatives = mod.operatives; - } - if (mod.appliesTo) { - input.dataset.appliesTo = mod.appliesTo; - } - if (mod.appliesNotTo) { - input.dataset.appliesNotTo = mod.appliesNotTo; - } - label.appendChild(input); - label.appendChild(document.createTextNode(mod.label)); - const modifierItem = document.createElement('div'); - modifierItem.className = 'modifier-item'; - modifierItem.appendChild(label); - section.appendChild(modifierItem); - }); - return section; - }; - const container = document.createElement('div'); - container.id = 'modifiers'; - container.appendChild(createModifierSection('Item Buffs', modifiers.itemBuffs, 'items')); - container.appendChild(createModifierSection('Perks', modifiers.operativePerks, 'perks')); - modifiersContainer.appendChild(container); - const abilitiesSection = createModifierSection('Abilities', modifiers.abilityPerks, 'abilities'); - modifiersContainer.appendChild(abilitiesSection); - modifiersContainer.addEventListener('change', applyModifiers); -} - -function applyModifiers() { - if (!selectedWeapon) return; - const hasNVG = !!document.querySelector( - "#modifiers input[id='damage-item-multiplicative-nightvision']:checked" - ); - let baseDamage = selectedWeapon.damage; - let baseRpm = selectedWeapon.rpm; - let baseReloadTime = selectedWeapon.reload_time; - let damageModifier = {additive: 0, multiplicative: 1}; - let rpmModifier = {additive: 0, multiplicative: 1}; - let reloadTimeModifier = {additive: 0, multiplicative: 1}; - headshotMultiplier = 0; - headshotBaseApplied = false; - ignoreHeadshots = hasNVG; - - function processModifier(mod) { - if (mod.dataset.operatives && mod.dataset.operatives.trim() !== "" && (!selectedOperative || selectedOperative.category !== mod.dataset.operatives)) { - return; - } - const weaponTags = selectedWeapon.guntype; - const appliesNotTo = mod.dataset.appliesNotTo; - const appliesTo = mod.dataset.appliesTo; - if (appliesNotTo && weaponTags.includes(appliesNotTo)) { - return; - } - if (appliesTo && !weaponTags.includes(appliesTo)) { - return; - } - if (mod.appliesNotTo && weaponTags.includes(mod.appliesNotTo)) { - return; - } - if (mod.appliesTo && !weaponTags.includes(mod.appliesTo)) { - return; - } - if (mod.dataset.type === 'multiplicative-headshot') { - // Only apply headshot multipliers if the base headshot modifier is active - if (headshotBaseApplied) { - headshotMultiplier += parseFloat(mod.value) - 1; - } - } else if (mod.dataset.type === 'multiplicative-headshot-base') { - if (!headshotBaseApplied) { - headshotMultiplier += parseFloat(mod.value) - 1; - headshotBaseApplied = true; - } - } else { - applyBuff(mod, rpmModifier, reloadTimeModifier, damageModifier); - } - if (mod.dataset.buffs) { - const buffs = JSON.parse(mod.dataset.buffs); - buffs.forEach(buff => { - applyBuff(buff, rpmModifier, reloadTimeModifier, damageModifier); - }); - } else { - const modType = mod.dataset.type; - const modValue = parseFloat(mod.value); - const singleBuff = { - type: modType === 'multiplicative' ? 'multiplicative' : 'additive', - buffType: modType, - value: modValue - }; - if (mod.id.includes('damage')) { - singleBuff.type = 'damage'; - } else if (mod.id.includes('rpm')) { - singleBuff.type = 'rpm'; - } else if (mod.id.includes('reload')) { - singleBuff.type = 'reloadTime'; - } - applyBuff(singleBuff, rpmModifier, reloadTimeModifier, damageModifier); - } - } - - //process operative-specific modifiers - const processOperativeModifier = (mod) => { - const weaponTags = selectedWeapon.guntype; //this is now an array - if (mod.appliesNotTo && weaponTags.includes(mod.appliesNotTo)) { - return; - } - if (mod.appliesTo && !weaponTags.includes(mod.appliesTo)) { - return; - } - if (mod.buffs) { - mod.buffs.forEach(buff => { - applyBuff(buff, rpmModifier, reloadTimeModifier, damageModifier); - }); - } else { - const modValue = parseFloat(mod.value); - const modType = mod.type || 'additive'; - const singleBuff = { - type: mod.id.includes('damage') ? 'damage' : mod.id.includes('rpm') ? 'rpm' : 'reloadTime', - buffType: modType === 'multiplicative' ? 'multiplicative' : 'additive', - value: modValue - }; - applyBuff(singleBuff, rpmModifier, reloadTimeModifier, damageModifier); - } - }; - const abilitiesSection = document.getElementById('abilities'); - abilitiesSection.querySelectorAll('input:checked').forEach(mod => { - processModifier(mod); // Process the ability modifier the same way as global modifiers - }); - const modifiersContainer = document.getElementById('modifiers'); - modifiersContainer.removeEventListener('change', applyModifiers); - document.querySelectorAll('#modifiers input:checked').forEach(mod => { - processModifier(mod); - }); - modifiersContainer.addEventListener('change', applyModifiers); - const operativeSection = document.getElementById('operative-modifiers'); - operativeSection.querySelectorAll('input:checked').forEach(mod => { - if (selectedOperative && selectedOperative.modifiers) { - const modObj = selectedOperative.modifiers.find(m => m.id === mod.id); - if (modObj) { - processOperativeModifier(modObj); - } - } - }); - let damage = baseDamage; - damage *= damageModifier.multiplicative; - if (headshotMultiplier > 0) { - damage *= (1 + headshotMultiplier); - } - damage += damageModifier.additive; - damage *= selectedWeapon.damage_multiplier; - let rpm = baseRpm * rpmModifier.multiplicative + rpmModifier.additive; - let reloadTime = baseReloadTime * reloadTimeModifier.multiplicative + reloadTimeModifier.additive; - if (reloadTime < 0.1) { - reloadTime = 0.1; - } - document.getElementById('damage-value').innerText = damage.toFixed(2); - document.getElementById('rpm-value').innerText = rpm.toFixed(2); - document.getElementById('reload-value').innerText = - reloadTime.toFixed(2) + ' seconds'; - calculateDPSAndDPM(damage, rpm, selectedWeapon.mag_size, reloadTime); -} - -function applyBuff(buff, rpmModifier, reloadTimeModifier, damageModifier) { - if (ignoreHeadshots && - (buff.buffType === 'multiplicative-headshot' || - buff.buffType === 'multiplicative-headshot-base')) { - return; - } - const {type, buffType, value} = buff; - if (buffType === 'multiplicative-headshot-base' && !headshotBaseApplied) { - headshotMultiplier = value - 1; - headshotBaseApplied = true; - return; - } - if (buffType === 'multiplicative-headshot') { - // Only apply if base headshot modifier is active - if (headshotBaseApplied) { - headshotMultiplier += value - 1; - } - return; - } - if (type === 'rpm') { - if (buffType === 'multiplicative') rpmModifier.multiplicative *= value; - else if (buffType === 'additive') rpmModifier.additive += value; - } else if (type === 'reloadTime') { - if (buffType === 'multiplicative') reloadTimeModifier.multiplicative *= value; - else if (buffType === 'additive') reloadTimeModifier.additive += value; - } else if (type === 'damage' && damageModifier) { - if (buffType === 'multiplicative') damageModifier.multiplicative *= value; - else if (buffType === 'additive') damageModifier.additive += value; - } -} - -//consider gun type checkboxes -function filterWeapons() { - const input = document.getElementById('weapon-search').value.toLowerCase(); - const weaponSelect = document.getElementById('weapon-select'); - const options = weaponSelect.getElementsByTagName('option'); - const selectedGuntypes = Array.from(document.querySelectorAll('#guntype-filters input:checked')) - .map(checkbox => checkbox.value); - let firstVisibleOption = null; - for (let i = 0; i < options.length; i++) { - const optionText = options[i].textContent || options[i].innerText; - const weaponId = parseInt(options[i].value); - const weapon = weapons.find(w => w.id === weaponId); - if (!weapon) continue; - const matchesSearch = optionText.toLowerCase().indexOf(input) > -1; - const matchesGuntypes = selectedGuntypes.length === 0 || selectedGuntypes.every(type => weapon.guntype.includes(type)); - if (matchesSearch && matchesGuntypes) { - options[i].style.display = ''; - if (!firstVisibleOption) { - firstVisibleOption = options[i]; - } - } else { - options[i].style.display = 'none'; - } - } - if (firstVisibleOption) { - weaponSelect.value = firstVisibleOption.value; - weaponSelect.dispatchEvent(new Event('change')); - } -} - -//filter operatives in the dropdown based on search input -function filterOperatives() { - const input = document.getElementById('operative-search').value.toLowerCase(); - const operativeSelect = document.getElementById('operative-select'); - const options = operativeSelect.getElementsByTagName('option'); - let firstVisibleOption = null; - for (let i = 0; i < options.length; i++) { - const optionText = options[i].textContent || options[i].innerText; - if (optionText.toLowerCase().indexOf(input) > -1) { - options[i].style.display = ''; - if (!firstVisibleOption) { - firstVisibleOption = options[i]; - } - } else { - options[i].style.display = 'none'; - } - } - if (firstVisibleOption) { - operativeSelect.value = firstVisibleOption.value; - operativeSelect.dispatchEvent(new Event('change')); - } -} - -//create gun type checkboxes based on available gun types in the weapon data -function createGunTypeCheckboxes() { - const gunTypeFiltersContainer = document.getElementById('gun-type-filters'); - gunTypeFiltersContainer.innerHTML = ''; - const gunTypesSet = new Set(); - weapons.forEach(weapon => { - weapon.guntype.forEach(type => gunTypesSet.add(type)); - }); - gunTypesSet.forEach(gunType => { - const label = document.createElement('label'); - const input = document.createElement('input'); - input.type = 'checkbox'; - input.value = gunType; - input.className = 'gun-type-checkbox'; - input.addEventListener('change', () => { - const selectedCheckboxes = Array.from(document.querySelectorAll('.gun-type-checkbox:checked')); - selectedGunTypes = selectedCheckboxes.map(checkbox => checkbox.value); - filterWeapons(); - }); - label.appendChild(input); - label.appendChild(document.createTextNode(gunType)); - gunTypeFiltersContainer.appendChild(label); - gunTypeFiltersContainer.appendChild(document.createElement('br')); - }); -} - -document.getElementById('weapon-search').addEventListener('keyup', filterWeapons); -document.getElementById('operative-search').addEventListener('keyup', filterOperatives); -document.querySelectorAll('#guntype-filters input').forEach(checkbox => { - checkbox.addEventListener('change', filterWeapons); -}); - -//Create/Edit a Weapon -const openBtn = document.getElementById('weapon-create'); -const closeBtn = document.getElementById('weapon-cancel'); -const modal = document.getElementById('weapon-form'); -const form = document.getElementById('weaponForm'); -const editBtn = document.getElementById('weapon-edit'); -const editError = document.getElementById('edit-error'); -const submitBtn = document.getElementById('submit-btn') - -//Show the modal -openBtn.addEventListener('click', () => { - modal.style.display = 'flex'; -}); - -closeBtn.addEventListener('click', () => { - modal.style.display = 'none'; - form.reset(); - isEditing = false; -}); - -modal.addEventListener('click', e => { - if (e.target === modal) { - modal.style.display = 'none'; - form.reset(); - isEditing = false; - } -}); - - -//Handle form submission -form.addEventListener('submit', e => { - e.preventDefault(); +const calculate = () => { const data = new FormData(form); - const types = Array.from(form.querySelectorAll('input[name="gun-type"]:checked')) - .map(cb => cb.value); - if (types.length === 0) { - alert('Please select at least one weapon type.'); + const baseDamage = numberOrNull(data.get('baseDamage')); + const projectiles = numberOrNull(data.get('projectiles')) ?? 1; + const rpm = numberOrNull(data.get('rpm')); + const magazine = numberOrNull(data.get('magazine')); + const reload = numberOrNull(data.get('reload')); + const targetHealth = numberOrNull(data.get('targetHealth')); + + if (!baseDamage || !rpm) { + Object.values(outputs).forEach((node) => { + node.textContent = '--'; + }); return; } - if (isEditing) { - selectedWeapon.name = data.get('name'); - selectedWeapon.damage = parseFloat(data.get('damage')); - selectedWeapon.rpm = parseFloat(data.get('rpm')); - selectedWeapon.mag_size = parseFloat(data.get('magazin')); - selectedWeapon.reload_time = parseFloat(data.get('reload-time')); - selectedWeapon.damage_multiplier = parseFloat(data.get('multiplier')); - selectedWeapon.guntype = types; - } else { - const newId = weapons.length - ? Math.max(...weapons.map(w => w.id)) + 1 - : 1; - weapons.push({ - id: newId, - name: data.get('name'), - damage: parseFloat(data.get('damage')), - rpm: parseFloat(data.get('rpm')), - mag_size: parseFloat(data.get('magazin')), - reload_time: parseFloat(data.get('reload-time')), - damage_multiplier: parseFloat(data.get('multiplier')), - guntype: types - }); + + const shotDamage = baseDamage * projectiles; + const shotsPerSecond = rpm / 60; + const burstDps = shotDamage * shotsPerSecond; + const magDamage = magazine ? shotDamage * magazine : null; + const timeToEmpty = magazine ? magazine / shotsPerSecond : null; + + let sustainedDps = null; + if (magDamage !== null && reload !== null && timeToEmpty !== null && (timeToEmpty + reload) > 0) { + sustainedDps = magDamage / (timeToEmpty + reload); } - populateWeapons(); - const weaponSelect = document.getElementById('weapon-select'); - weaponSelect.value = selectedWeapon.id; - weaponSelect.dispatchEvent(new Event('change')); - modal.style.display = 'none'; + + let ttk = null; + if (targetHealth && burstDps > 0) { + ttk = targetHealth / burstDps; + } + + outputs.shotDamage.textContent = formatValue(shotDamage); + outputs.burstDps.textContent = formatValue(burstDps); + outputs.sustainedDps.textContent = formatValue(sustainedDps); + outputs.timeToEmpty.textContent = formatValue(timeToEmpty, 's'); + outputs.magDamage.textContent = formatValue(magDamage); + outputs.ttk.textContent = formatValue(ttk, 's'); +}; + +form.addEventListener('input', calculate); + +resetButton.addEventListener('click', () => { form.reset(); - isEditing = false; - editBtn.disabled = true; + calculate(); }); -document.getElementById('copy-stats').addEventListener('click', async () => { - const statsTable = document.querySelector('#weapon-stats table tr + tr'); - if (!statsTable) { - showCopyMessage('No weapon selected to copy.', 'error'); - return; - } - const cells = Array.from(statsTable.querySelectorAll('td')).map(td => td.innerText.trim()); - const [name, dmg, rpm, mag, reload, dps, dpm, avgDps] = cells; - const globalMods = Array.from( - document.querySelectorAll('#modifiers label input:checked') - ).map(cb => cb.parentNode.textContent.trim()); - const opMods = Array.from( - document.querySelectorAll('#operative-modifiers label input:checked') - ).map(cb => cb.parentNode.textContent.trim()); - const allMods = globalMods.concat(opMods); - const modsString = allMods.length - ? allMods.join(', ') - : 'None'; +copyButton.addEventListener('click', async () => { + const data = new FormData(form); + const name = data.get('weaponName') || 'Unnamed loadout'; const payload = [ - `Weapon: ${name}`, - `Damage: ${dmg}`, - `RPM: ${rpm}`, - `Mag Size: ${mag}`, - `Reload Time: ${reload}`, - `DPS: ${dps}`, - `DPM: ${dpm}`, - `Average DPS: ${avgDps}`, - `Modifiers used for this calculation: ${modsString}` + `Loadout: ${name}`, + `Damage per shot: ${outputs.shotDamage.textContent}`, + `Burst DPS: ${outputs.burstDps.textContent}`, + `Sustained DPS: ${outputs.sustainedDps.textContent}`, + `Time to empty: ${outputs.timeToEmpty.textContent}`, + `Magazine damage: ${outputs.magDamage.textContent}`, + `Time to eliminate: ${outputs.ttk.textContent}` ].join('\n'); + try { await navigator.clipboard.writeText(payload); - showCopyMessage('Stats copied to clipboard!', 'success'); - } catch (err) { - console.error(err); - showCopyMessage('Failed to copy.', 'error'); + copyButton.textContent = 'Copied!'; + setTimeout(() => { + copyButton.textContent = 'Copy summary'; + }, 2000); + } catch (error) { + console.error('Clipboard copy failed', error); + copyButton.textContent = 'Copy failed'; + setTimeout(() => { + copyButton.textContent = 'Copy summary'; + }, 2000); } }); -editBtn.addEventListener('click', () => { - if (!selectedWeapon) { - editError.textContent = 'You must select a weapon first.'; - return; - } - editError.textContent = ''; - isEditing = true; - isEditing = true; - form.elements['name'].value = selectedWeapon.name; - form.elements['damage'].value = selectedWeapon.damage; - form.elements['rpm'].value = selectedWeapon.rpm; - form.elements['magazin'].value = selectedWeapon.mag_size; - form.elements['reload-time'].value = selectedWeapon.reload_time; - form.elements['multiplier'].value = selectedWeapon.damage_multiplier; - form.querySelectorAll('input[name="gun-type"]').forEach(cb => { - cb.checked = selectedWeapon.guntype.includes(cb.value); - }); - modal.style.display = 'flex'; -}); - -function showCopyMessage(text, type) { - const msg = document.getElementById('copy-message'); - msg.textContent = text; - msg.className = type; - setTimeout(() => { - msg.textContent = ''; - msg.className = ''; - }, 3000); -} - - -// Initialize -fetchWeaponsData(); -fetchModifiers(); -fetchOperativesData() \ No newline at end of file +calculate(); diff --git a/static/img/TRL_Logo.jpg b/static/img/TRL_Logo.jpg deleted file mode 100644 index 4836f40..0000000 Binary files a/static/img/TRL_Logo.jpg and /dev/null differ diff --git a/static/img/TRL_Logo_Custom.jpg b/static/img/TRL_Logo_Custom.jpg deleted file mode 100644 index 174746e..0000000 Binary files a/static/img/TRL_Logo_Custom.jpg and /dev/null differ diff --git a/static/img/menu-list-icon-app-navigation-option-page-interface-dropdown-three-lines-button-dots-sign-symbol-black-artwork-graphic-illustration-clipart-eps-vector.jpg b/static/img/menu-list-icon-app-navigation-option-page-interface-dropdown-three-lines-button-dots-sign-symbol-black-artwork-graphic-illustration-clipart-eps-vector.jpg deleted file mode 100644 index 02c1bd4..0000000 Binary files a/static/img/menu-list-icon-app-navigation-option-page-interface-dropdown-three-lines-button-dots-sign-symbol-black-artwork-graphic-illustration-clipart-eps-vector.jpg and /dev/null differ diff --git a/static/img/trlbg1.jpg b/static/img/trlbg1.jpg deleted file mode 100644 index d858c66..0000000 Binary files a/static/img/trlbg1.jpg and /dev/null differ diff --git a/static/style.css b/static/style.css index 7fc3e07..fd1c274 100644 --- a/static/style.css +++ b/static/style.css @@ -27,14 +27,13 @@ input[type="text"] { } body { - background-image: url("/trlstats/static/img/trlbg1.jpg"); - background-repeat: no-repeat; + background: radial-gradient(circle at top, #0f172a 0%, #0b1120 45%, #020617 100%); background-attachment: fixed; - background-size: cover; position: relative; z-index: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif; margin: 20px; + color: #e2e8f0; } header nav { @@ -784,7 +783,7 @@ body::before { content: ""; position: fixed; inset: 0; - background: linear-gradient(180deg, rgba(2, 6, 23, 0.35), rgba(2, 6, 23, 0.25)), var(--bg-tint); + background: linear-gradient(180deg, rgba(2, 6, 23, 0.45), rgba(2, 6, 23, 0.25)); pointer-events: none; z-index: 0; } @@ -1060,3 +1059,284 @@ th, td { flex: 1; } } + +/* -------------------------- + Anomaly Front calculator + -------------------------- */ +.af-body { + margin: 0; + padding: 32px; +} + +.af-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + background: rgba(15, 23, 42, 0.7); + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 20px; + padding: 20px 28px; + margin-bottom: 24px; + position: relative; + z-index: 2; + backdrop-filter: blur(12px); +} + +.af-brand { + display: flex; + align-items: center; + gap: 16px; +} + +.af-badge { + font-size: 0.9rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + background: rgba(59, 130, 246, 0.2); + color: #bae6fd; + padding: 6px 12px; + border-radius: 999px; + border: 1px solid rgba(59, 130, 246, 0.35); +} + +.af-title { + font-size: 1.5rem; + margin: 0; + color: #f8fafc; + font-weight: 600; +} + +.af-subtitle { + margin: 4px 0 0; + color: #cbd5f5; + font-size: 0.95rem; +} + +.af-nav { + display: flex; + gap: 16px; + flex-wrap: wrap; +} + +.af-nav a { + color: #e2e8f0; + font-weight: 500; + padding: 8px 14px; + border-radius: 999px; + border: 1px solid rgba(148, 163, 184, 0.2); + background: rgba(15, 23, 42, 0.4); +} + +.af-nav a:hover { + color: #fff; + background: rgba(59, 130, 246, 0.3); + text-decoration: none; +} + +.af-shell { + display: flex; + flex-direction: column; + gap: 24px; + position: relative; + z-index: 2; +} + +.af-hero { + display: flex; + flex-wrap: wrap; + gap: 24px; + align-items: center; + justify-content: space-between; + background: rgba(15, 23, 42, 0.65); + border-radius: 24px; + padding: 28px; + border: 1px solid rgba(148, 163, 184, 0.2); + box-shadow: 0 20px 40px rgba(2, 6, 23, 0.4); +} + +.af-hero h1 { + margin-top: 0; + color: #f8fafc; +} + +.af-hero p { + color: #cbd5f5; + max-width: 560px; +} + +.af-status { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 16px; + width: min(520px, 100%); +} + +.af-status-label { + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.08em; + color: #94a3b8; + margin-bottom: 6px; +} + +.af-status-value { + margin: 0; + font-weight: 600; + color: #e2e8f0; +} + +.af-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 24px; +} + +.af-card { + background: rgba(15, 23, 42, 0.65); + border-radius: 20px; + padding: 24px; + border: 1px solid rgba(148, 163, 184, 0.2); + box-shadow: 0 16px 30px rgba(2, 6, 23, 0.35); +} + +.af-card-header h2 { + margin-top: 0; + color: #f8fafc; +} + +.af-card-header p { + color: #cbd5f5; + margin-bottom: 20px; +} + +.af-form { + display: grid; + gap: 14px; +} + +.af-form label { + display: grid; + gap: 6px; + color: #e2e8f0; + font-weight: 500; +} + +.af-form input { + background: rgba(15, 23, 42, 0.8); + border: 1px solid rgba(148, 163, 184, 0.3); + color: #f8fafc; +} + +.af-form-actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-top: 6px; +} + +.af-form-actions button { + background: rgba(59, 130, 246, 0.9); + color: #f8fafc; + border: none; + border-radius: 999px; + padding: 10px 18px; + font-weight: 600; +} + +#af-reset { + background: rgba(148, 163, 184, 0.4); +} + +.af-metrics { + display: grid; + gap: 16px; +} + +.af-metric { + background: rgba(2, 6, 23, 0.35); + border-radius: 16px; + padding: 16px; + border: 1px solid rgba(148, 163, 184, 0.15); +} + +.af-metric-label { + text-transform: uppercase; + font-size: 0.75rem; + letter-spacing: 0.08em; + color: #94a3b8; + margin-bottom: 8px; +} + +.af-metric-value { + margin: 0; + font-size: 1.4rem; + font-weight: 600; + color: #f8fafc; +} + +.af-callout { + margin-top: 20px; + background: rgba(59, 130, 246, 0.15); + border-radius: 16px; + padding: 16px; + border: 1px solid rgba(59, 130, 246, 0.35); +} + +.af-callout h3 { + margin-top: 0; + color: #e0f2fe; +} + +.af-callout ul { + margin: 0; + padding-left: 18px; + color: #cbd5f5; +} + +.af-empty { + display: grid; + gap: 12px; + color: #cbd5f5; + background: rgba(2, 6, 23, 0.3); + border-radius: 16px; + padding: 18px; + border: 1px dashed rgba(148, 163, 184, 0.3); +} + +.af-muted-button { + background: rgba(148, 163, 184, 0.2); + border-radius: 999px; + padding: 10px 16px; + color: #e2e8f0; + border: none; + width: fit-content; +} + +.af-notes { + width: 100%; + min-height: 180px; + border-radius: 16px; + padding: 16px; + background: rgba(15, 23, 42, 0.8); + border: 1px solid rgba(148, 163, 184, 0.3); + color: #f8fafc; + resize: vertical; +} + +@media (max-width: 720px) { + .af-body { + padding: 20px; + } + + .af-header { + flex-direction: column; + align-items: flex-start; + } + + .af-nav { + width: 100%; + justify-content: flex-start; + } +} diff --git a/templates/about_us.html b/templates/about_us.html index 71f5b27..c0391de 100644 --- a/templates/about_us.html +++ b/templates/about_us.html @@ -4,7 +4,6 @@ About Us - TRL Stats - @@ -114,4 +113,4 @@ ); - \ No newline at end of file + diff --git a/templates/calculator.html b/templates/calculator.html index 24e8c9c..fca3fa9 100644 --- a/templates/calculator.html +++ b/templates/calculator.html @@ -3,200 +3,153 @@ - - DPM/DPS Calculator + Anomaly Front | Combat Calculator -
-