diff --git a/static/filterWeapons.js b/static/filterWeapons.js index 4658696..db191fb 100644 --- a/static/filterWeapons.js +++ b/static/filterWeapons.js @@ -1,4 +1,5 @@ const form = document.getElementById('af-calculator'); +const weaponSelect = document.getElementById('af-weapon-select'); const outputs = { shotDamage: document.querySelector('[data-output="shotDamage"]'), burstDps: document.querySelector('[data-output="burstDps"]'), @@ -10,6 +11,7 @@ const outputs = { const resetButton = document.getElementById('af-reset'); const copyButton = document.getElementById('af-copy'); +const weaponsById = new Map(); const numberOrNull = (value) => { const parsed = Number.parseFloat(value); @@ -64,6 +66,72 @@ const calculate = () => { outputs.ttk.textContent = formatValue(ttk, 's'); }; +const setFormValue = (name, value) => { + const field = form.elements[name]; + if (!field) { + return; + } + field.value = value ?? ''; +}; + +const applyWeapon = (weapon) => { + if (!weapon) { + return; + } + setFormValue('weaponName', weapon.name ?? ''); + setFormValue('baseDamage', weapon.damage ?? ''); + setFormValue('projectiles', weapon.damage_multiplier ?? 1); + setFormValue('rpm', weapon.rpm ?? ''); + setFormValue('magazine', weapon.mag_size ?? ''); + setFormValue('reload', weapon.reload_time ?? ''); + calculate(); +}; + +const loadWeaponLibrary = async () => { + if (!weaponSelect) { + return; + } + const source = weaponSelect.dataset.source; + if (!source) { + return; + } + + try { + const response = await fetch(source); + if (!response.ok) { + throw new Error('Failed to load weapons'); + } + const weapons = await response.json(); + const normalized = weapons + .filter((weapon) => weapon && weapon.name) + .map((weapon) => ({ + id: weapon.id ?? weapon.name, + name: weapon.name, + damage: weapon.damage, + damage_multiplier: weapon.damage_multiplier ?? 1, + rpm: weapon.rpm, + mag_size: weapon.mag_size, + reload_time: weapon.reload_time + })) + .sort((a, b) => a.name.localeCompare(b.name)); + + normalized.forEach((weapon) => { + const key = String(weapon.id); + weaponsById.set(key, weapon); + const option = document.createElement('option'); + option.value = key; + option.textContent = weapon.name; + weaponSelect.appendChild(option); + }); + } catch (error) { + console.error('Weapon library load failed', error); + const option = document.createElement('option'); + option.textContent = 'Weapon library unavailable'; + option.disabled = true; + weaponSelect.appendChild(option); + } +}; + form.addEventListener('input', calculate); resetButton.addEventListener('click', () => { @@ -71,6 +139,16 @@ resetButton.addEventListener('click', () => { calculate(); }); +if (weaponSelect) { + weaponSelect.addEventListener('change', (event) => { + const selected = weaponsById.get(event.target.value); + if (!selected) { + return; + } + applyWeapon(selected); + }); +} + copyButton.addEventListener('click', async () => { const data = new FormData(form); const name = data.get('weaponName') || 'Unnamed loadout'; @@ -100,3 +178,4 @@ copyButton.addEventListener('click', async () => { }); calculate(); +loadWeaponLibrary(); diff --git a/static/style.css b/static/style.css index 95f492e..878d9d5 100644 --- a/static/style.css +++ b/static/style.css @@ -518,6 +518,353 @@ body.menu-open { box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35); } +.af-body { + margin: 0; + min-height: 100vh; + background: radial-gradient(circle at top, #1e293b 0%, #0f172a 45%, #020617 100%); + color: #e2e8f0; + font-family: "Inter", "Segoe UI", system-ui, sans-serif; +} + +.af-header { + position: sticky; + top: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: space-between; + gap: 24px; + padding: 20px 32px; + background: rgba(15, 23, 42, 0.92); + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + backdrop-filter: blur(12px); +} + +.af-brand { + display: flex; + align-items: center; + gap: 16px; +} + +.af-logo { + width: 44px; + height: 44px; + border-radius: 16px; + padding: 8px; + display: grid; + gap: 6px; + background: linear-gradient(135deg, rgba(56, 189, 248, 0.3), rgba(94, 234, 212, 0.1)); + border: 1px solid rgba(56, 189, 248, 0.35); +} + +.af-logo span { + display: block; + height: 4px; + border-radius: 999px; + background: linear-gradient(90deg, #38bdf8, #22c55e); +} + +.af-title { + font-size: 1.2rem; + font-weight: 600; + margin: 0; +} + +.af-subtitle { + margin: 4px 0 0; + color: #94a3b8; + font-size: 0.95rem; +} + +.af-nav { + display: flex; + align-items: center; + gap: 12px; +} + +.af-nav-link { + padding: 8px 16px; + border-radius: 999px; + text-decoration: none; + font-weight: 600; + color: #e2e8f0; + background: rgba(148, 163, 184, 0.12); + border: 1px solid transparent; + transition: all 0.2s ease; +} + +.af-nav-link:hover { + border-color: rgba(56, 189, 248, 0.4); + background: rgba(56, 189, 248, 0.2); + color: #f8fafc; +} + +.af-nav-toggle { + display: none; + padding: 8px 16px; + border-radius: 999px; + border: 1px solid rgba(148, 163, 184, 0.4); + background: transparent; + color: #e2e8f0; + font-weight: 600; +} + +.af-shell { + max-width: 1200px; + margin: 32px auto 80px; + padding: 0 24px; + background: transparent; + box-shadow: none; + border: none; +} + +.af-hero { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 24px; + margin-bottom: 32px; +} + +.af-hero h1, +.af-card h1 { + color: #f8fafc; + margin-top: 0; +} + +.af-hero p { + color: #cbd5f5; + line-height: 1.6; +} + +.af-status { + display: grid; + gap: 16px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 16px; + padding: 20px; +} + +.af-status-label { + font-size: 0.75rem; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #94a3b8; + margin: 0; +} + +.af-status-value { + margin: 4px 0 0; + font-weight: 600; +} + +.af-grid { + display: grid; + gap: 24px; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + margin-bottom: 32px; +} + +.af-card { + background: rgba(15, 23, 42, 0.75); + border-radius: 20px; + border: 1px solid rgba(148, 163, 184, 0.2); + padding: 24px; + box-shadow: 0 20px 45px rgba(15, 23, 42, 0.35); +} + +.af-card-header h2 { + margin: 0 0 8px; + color: #f8fafc; +} + +.af-card-header p { + margin: 0; + color: #cbd5f5; + line-height: 1.6; +} + +.af-card-actions { + margin-top: 20px; +} + +.af-cta { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + border-radius: 999px; + background: linear-gradient(120deg, #38bdf8, #22c55e); + color: #0f172a; + font-weight: 700; + text-decoration: none; + border: none; + cursor: pointer; +} + +.af-cta:hover { + filter: brightness(1.05); +} + +.af-form { + display: grid; + gap: 16px; + margin-top: 16px; +} + +.af-form label { + display: flex; + flex-direction: column; + gap: 8px; + font-weight: 600; + color: #e2e8f0; +} + +.af-form input, +.af-form select, +.af-form textarea, +.af-input { + padding: 12px 14px; + border-radius: 12px; + border: 1px solid rgba(148, 163, 184, 0.3); + background: rgba(15, 23, 42, 0.7); + color: #f8fafc; + font-size: 0.95rem; +} + +.af-form input:focus, +.af-form select:focus, +.af-form textarea:focus, +.af-input:focus { + outline: 2px solid rgba(56, 189, 248, 0.6); + border-color: rgba(56, 189, 248, 0.6); +} + +.af-field-hint { + font-size: 0.85rem; + font-weight: 400; + color: #94a3b8; +} + +.af-form-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 8px; +} + +.af-form-actions button { + padding: 10px 18px; + border-radius: 999px; + border: 1px solid rgba(148, 163, 184, 0.3); + background: rgba(148, 163, 184, 0.12); + color: #e2e8f0; + cursor: pointer; +} + +.af-form-actions button:hover { + background: rgba(56, 189, 248, 0.25); + border-color: rgba(56, 189, 248, 0.4); +} + +.af-metrics { + display: grid; + gap: 16px; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + margin-top: 12px; +} + +.af-metric { + padding: 16px; + border-radius: 16px; + background: rgba(2, 6, 23, 0.6); + border: 1px solid rgba(148, 163, 184, 0.2); +} + +.af-metric-label { + margin: 0 0 6px; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #94a3b8; +} + +.af-metric-value { + margin: 0; + font-size: 1.4rem; + font-weight: 700; + color: #f8fafc; +} + +.af-callout { + margin-top: 20px; + padding: 16px; + border-radius: 16px; + background: rgba(56, 189, 248, 0.1); + border: 1px solid rgba(56, 189, 248, 0.3); +} + +.af-callout h3 { + margin-top: 0; +} + +.af-empty { + display: grid; + gap: 12px; + padding: 16px; + border-radius: 16px; + background: rgba(2, 6, 23, 0.6); + border: 1px dashed rgba(148, 163, 184, 0.3); +} + +.af-muted-button { + padding: 8px 14px; + border-radius: 999px; + border: 1px solid rgba(148, 163, 184, 0.3); + background: transparent; + color: #e2e8f0; +} + +.af-notes { + width: 100%; + min-height: 160px; + border-radius: 16px; + padding: 14px; + background: rgba(15, 23, 42, 0.7); + border: 1px solid rgba(148, 163, 184, 0.3); + color: #f8fafc; +} + +.af-copy-grid { + display: grid; + gap: 20px; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + margin-top: 20px; +} + +@media (max-width: 820px) { + .af-header { + flex-wrap: wrap; + } + + .af-nav { + display: none; + width: 100%; + flex-direction: column; + align-items: flex-start; + padding-top: 12px; + } + + .af-nav.is-open { + display: flex; + } + + .af-nav-toggle { + display: inline-flex; + margin-left: auto; + } +} + #weapon-form h2 { margin-top: 0; color: #222; @@ -1059,418 +1406,3 @@ 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: linear-gradient(135deg, rgba(15, 23, 42, 0.85), rgba(30, 41, 59, 0.6)); - border: 1px solid rgba(148, 163, 184, 0.25); - border-radius: 24px; - padding: 20px 28px; - margin-bottom: 24px; - position: relative; - z-index: 2; - backdrop-filter: blur(12px); -} - -.af-header::before { - content: ""; - position: absolute; - inset: 0; - border-radius: 24px; - padding: 1px; - background: linear-gradient(120deg, rgba(56, 189, 248, 0.6), rgba(59, 130, 246, 0.2), rgba(14, 116, 144, 0.5)); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; -} - -.af-brand { - display: flex; - align-items: center; - gap: 16px; -} - -.af-logo { - width: 44px; - height: 44px; - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(2, 1fr); - gap: 4px; - padding: 6px; - border-radius: 14px; - background: rgba(15, 23, 42, 0.8); - border: 1px solid rgba(148, 163, 184, 0.35); - box-shadow: 0 8px 20px rgba(2, 6, 23, 0.45); -} - -.af-logo span { - border-radius: 6px; - background: linear-gradient(135deg, rgba(56, 189, 248, 0.9), rgba(59, 130, 246, 0.6)); -} - -.af-logo span:nth-child(2) { - background: linear-gradient(135deg, rgba(14, 116, 144, 0.9), rgba(2, 132, 199, 0.6)); -} - -.af-logo span:nth-child(3) { - background: linear-gradient(135deg, rgba(37, 99, 235, 0.9), rgba(15, 23, 42, 0.6)); -} - -.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-toggle { - display: none; - align-items: center; - gap: 8px; - padding: 8px 14px; - border-radius: 999px; - background: rgba(15, 23, 42, 0.6); - border: 1px solid rgba(148, 163, 184, 0.35); - color: #e2e8f0; - font-weight: 600; - cursor: pointer; - z-index: 1; -} - -.af-nav { - display: flex; - gap: 16px; - flex-wrap: wrap; - z-index: 1; -} - -.af-nav-link { - position: relative; - color: #e2e8f0; - font-weight: 500; - padding: 8px 16px; - border-radius: 999px; - border: 1px solid rgba(148, 163, 184, 0.3); - background: rgba(15, 23, 42, 0.4); - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.af-nav-link::after { - content: ""; - position: absolute; - left: 16px; - right: 16px; - bottom: 6px; - height: 2px; - border-radius: 999px; - background: linear-gradient(90deg, rgba(56, 189, 248, 0), rgba(56, 189, 248, 0.9), rgba(56, 189, 248, 0)); - opacity: 0; - transition: opacity 0.2s ease; -} - -.af-nav-link:hover { - color: #fff; - background: rgba(59, 130, 246, 0.3); - box-shadow: 0 10px 20px rgba(14, 116, 144, 0.2); - transform: translateY(-1px); - text-decoration: none; -} - -.af-nav-link:hover::after { - opacity: 1; -} - -.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-actions { - margin-top: 18px; -} - -.af-cta { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 10px 18px; - border-radius: 999px; - background: linear-gradient(120deg, rgba(56, 189, 248, 0.9), rgba(59, 130, 246, 0.85)); - color: #f8fafc; - font-weight: 600; - border: 1px solid rgba(56, 189, 248, 0.6); - box-shadow: 0 12px 24px rgba(14, 116, 144, 0.35); -} - -.af-cta:hover { - color: #fff; - text-decoration: none; - transform: translateY(-1px); -} - -.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 input:focus, -.af-notes:focus, -.af-input:focus { - border-color: rgba(56, 189, 248, 0.7); - box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.35); - background: rgba(15, 23, 42, 0.92); - color: #f8fafc; -} - -.af-field { - display: grid; - gap: 6px; - color: #e2e8f0; - font-weight: 500; -} - -.af-input { - width: 100%; - padding: 12px 14px; - border-radius: 12px; - 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; -} - -.af-copy-grid { - display: grid; - gap: 18px; - color: #cbd5f5; -} - -.af-copy-grid ul { - padding-left: 18px; - margin: 8px 0 0; -} - -@media (max-width: 720px) { - .af-body { - padding: 20px; - } - - .af-header { - flex-direction: column; - align-items: flex-start; - } - - .af-nav-toggle { - display: inline-flex; - } - - .af-nav { - width: 100%; - flex-direction: column; - align-items: flex-start; - gap: 10px; - display: none; - } - - .af-nav.is-open { - display: flex; - } -} diff --git a/templates/about_us.html b/templates/about_us.html index c201573..58884d9 100644 --- a/templates/about_us.html +++ b/templates/about_us.html @@ -34,8 +34,8 @@
- This site is the staging ground for an unreleased experience. We are rebuilding every tool from the - ground up to avoid legacy stats, old branding, or outdated data. + This site is the staging ground for the toolkit. We are rebuilding every tool from the ground up to + avoid legacy stats, old branding, or outdated data.
Provide a shared workspace for balancing weapons, capturing loadout notes, and testing combat pacing - long before the official numbers land. + as the library grows.
- Anomaly Front is unreleased. Everything shown here is a placeholder designed to help us think - through combat pacing before the official data arrives. + Everything shown here is a placeholder designed to help us think through combat pacing while the + data set is still evolving.
- Anomaly Front is still unreleased, so this calculator starts from scratch. Add your own - placeholder values to explore pacing, time-to-eliminate, and reload cadence. + Start from a library weapon or add your own values to explore pacing, time-to-eliminate, and + reload cadence.
Stats library
-No official stats yet
+Community draft data
Modifier stack
@@ -48,7 +48,7 @@Version
-Foundation v0.1
+Foundation v0.2
Enter a prototype loadout and tweak values to see combat pacing.