feat: Added new injector chart page.
This commit is contained in:
201
app.py
201
app.py
@@ -751,5 +751,206 @@ def save_build():
|
|||||||
return jsonify({"build_id": build_id, "name": name})
|
return jsonify({"build_id": build_id, "name": name})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/meds")
|
||||||
|
def meds():
|
||||||
|
import requests as _req
|
||||||
|
|
||||||
|
API_URL = "https://api.tarkov.dev/graphql"
|
||||||
|
query = """
|
||||||
|
{
|
||||||
|
items(types: [injectors], lang: en) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
shortName
|
||||||
|
iconLink
|
||||||
|
wikiLink
|
||||||
|
properties {
|
||||||
|
__typename
|
||||||
|
... on ItemPropertiesStim {
|
||||||
|
useTime
|
||||||
|
cures
|
||||||
|
stimEffects {
|
||||||
|
type
|
||||||
|
skill { name }
|
||||||
|
value
|
||||||
|
percent
|
||||||
|
duration
|
||||||
|
delay
|
||||||
|
chance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resp = _req.post(API_URL, json={"query": query}, timeout=8)
|
||||||
|
raw_items = resp.json()["data"]["items"]
|
||||||
|
except Exception:
|
||||||
|
raw_items = []
|
||||||
|
|
||||||
|
# ── helper: pick first matching effect ──────────────────────────────
|
||||||
|
def pick(effects, type_str, positive_only=False, negative_only=False):
|
||||||
|
for e in effects:
|
||||||
|
if e["type"] == type_str:
|
||||||
|
if positive_only and e["value"] <= 0:
|
||||||
|
continue
|
||||||
|
if negative_only and e["value"] >= 0:
|
||||||
|
continue
|
||||||
|
return e
|
||||||
|
return None
|
||||||
|
|
||||||
|
def pick_skill(effects, skill_name):
|
||||||
|
for e in effects:
|
||||||
|
if e["type"] == "Skill" and e.get("skill") and e["skill"]["name"] == skill_name:
|
||||||
|
return e
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ── collect all skill names across all injectors ──────────────────
|
||||||
|
all_skills = []
|
||||||
|
for item in raw_items:
|
||||||
|
p = item.get("properties") or {}
|
||||||
|
for e in p.get("stimEffects", []):
|
||||||
|
if e["type"] == "Skill" and e.get("skill"):
|
||||||
|
sn = e["skill"]["name"]
|
||||||
|
if sn not in all_skills:
|
||||||
|
all_skills.append(sn)
|
||||||
|
skill_rows = sorted(all_skills)
|
||||||
|
|
||||||
|
# ── build injector data rows ──────────────────────────────────────
|
||||||
|
injectors = []
|
||||||
|
for item in raw_items:
|
||||||
|
p = item.get("properties") or {}
|
||||||
|
effs = p.get("stimEffects", [])
|
||||||
|
|
||||||
|
def _val(eff): return round(eff["value"], 2) if eff else None
|
||||||
|
def _dur(eff): return eff["duration"] if eff else 0
|
||||||
|
def _delay(eff): return eff["delay"] if eff else 0
|
||||||
|
|
||||||
|
hp_e = pick(effs, "Health regeneration", positive_only=True)
|
||||||
|
stam_e = pick(effs, "Stamina recovery", positive_only=True)
|
||||||
|
stam_neg_e = pick(effs, "Stamina recovery", negative_only=True)
|
||||||
|
stam_rec_e = stam_e or stam_neg_e
|
||||||
|
maxstam_e = pick(effs, "Max stamina")
|
||||||
|
weight_e = pick(effs, "Weight limit")
|
||||||
|
energy_e = pick(effs, "Energy recovery")
|
||||||
|
hydra_e = pick(effs, "Hydration recovery")
|
||||||
|
bleed_e = pick(effs, "Stops and prevents bleedings")
|
||||||
|
anti_e = pick(effs, "Antidote")
|
||||||
|
tremor_e = pick(effs, "Hands tremor")
|
||||||
|
tunnel_e = pick(effs, "Tunnel effect")
|
||||||
|
pain_e = pick(effs, "Pain")
|
||||||
|
temp_e = pick(effs, "Body temperature")
|
||||||
|
|
||||||
|
# skills dict
|
||||||
|
skills = {}
|
||||||
|
for sn in skill_rows:
|
||||||
|
se = pick_skill(effs, sn)
|
||||||
|
if se:
|
||||||
|
skills[sn] = {"value": round(se["value"], 1), "duration": se["duration"]}
|
||||||
|
|
||||||
|
# tags for column filtering
|
||||||
|
tags = []
|
||||||
|
if hp_e or bleed_e or anti_e:
|
||||||
|
tags.append("heal")
|
||||||
|
if maxstam_e or stam_rec_e:
|
||||||
|
tags.append("stam")
|
||||||
|
if skills:
|
||||||
|
tags.append("skill")
|
||||||
|
if temp_e or weight_e:
|
||||||
|
tags.append("special")
|
||||||
|
if not tags:
|
||||||
|
tags.append("special")
|
||||||
|
|
||||||
|
injectors.append({
|
||||||
|
"name": item["name"],
|
||||||
|
"short": item["shortName"],
|
||||||
|
"icon": item.get("iconLink"),
|
||||||
|
"wiki": item.get("wikiLink"),
|
||||||
|
"tags": ",".join(tags),
|
||||||
|
# healing
|
||||||
|
"hp_regen": _val(hp_e),
|
||||||
|
"hp_regen_dur": _dur(hp_e),
|
||||||
|
"stops_bleed": bool(bleed_e),
|
||||||
|
"antidote": bool(anti_e),
|
||||||
|
# stamina
|
||||||
|
"max_stam": _val(maxstam_e),
|
||||||
|
"max_stam_dur": _dur(maxstam_e),
|
||||||
|
"stam_rec": _val(stam_rec_e),
|
||||||
|
"stam_rec_dur": _dur(stam_rec_e),
|
||||||
|
# weight
|
||||||
|
"weight": _val(weight_e),
|
||||||
|
"weight_dur": _dur(weight_e),
|
||||||
|
# special
|
||||||
|
"body_temp": _val(temp_e),
|
||||||
|
"body_temp_dur": _dur(temp_e),
|
||||||
|
"energy": round(energy_e["value"], 2) if energy_e else None,
|
||||||
|
"energy_dur": _dur(energy_e),
|
||||||
|
"hydration": round(hydra_e["value"], 2) if hydra_e else None,
|
||||||
|
"hydration_dur": _dur(hydra_e),
|
||||||
|
# skills
|
||||||
|
"skills": skills,
|
||||||
|
# side effects
|
||||||
|
"tremor": bool(tremor_e),
|
||||||
|
"tremor_delay": _delay(tremor_e),
|
||||||
|
"tremor_dur": _dur(tremor_e),
|
||||||
|
"tunnel": bool(tunnel_e),
|
||||||
|
"tunnel_delay": _delay(tunnel_e),
|
||||||
|
"tunnel_dur": _dur(tunnel_e),
|
||||||
|
"pain": bool(pain_e),
|
||||||
|
"pain_delay": _delay(pain_e),
|
||||||
|
"pain_dur": _dur(pain_e),
|
||||||
|
})
|
||||||
|
|
||||||
|
# ── situation guide ───────────────────────────────────────────────
|
||||||
|
situations = {
|
||||||
|
"bleed": [
|
||||||
|
{"short": "Zagustin", "desc": "Stops bleeding, +Vitality 180s", "warn": "tremors delayed"},
|
||||||
|
{"short": "AHF1-M", "desc": "Stops bleeding, +Health 60s", "warn": "-hydration"},
|
||||||
|
{"short": "Perfotoran", "desc": "Stops bleed + antidote + regen", "warn": "-energy after"},
|
||||||
|
{"short": "xTG-12", "desc": "Antidote only (no bleed stop)", "warn": "-Health skill"},
|
||||||
|
],
|
||||||
|
"regen": [
|
||||||
|
{"short": "eTG-c", "desc": "+6.5 HP/s for 60s (fast burst)", "warn": "-energy after"},
|
||||||
|
{"short": "Adrenaline","desc": "+4 HP/s for 15s, stam boost", "warn": "-hydration after"},
|
||||||
|
{"short": "PNB", "desc": "+3 HP/s for 40s, +Strength", "warn": "tremors + skill debuff"},
|
||||||
|
{"short": "Propital", "desc": "+1 HP/s for 300s, skill buffs", "warn": "tremors at 270s"},
|
||||||
|
{"short": "Perfotoran","desc": "+1.5 HP/s for 60s + antidote", "warn": "-energy after"},
|
||||||
|
],
|
||||||
|
"stam": [
|
||||||
|
{"short": "Trimadol", "desc": "+3 stam rec, +10 max stam, 180s", "warn": "-energy/-hydration"},
|
||||||
|
{"short": "SJ6", "desc": "+2 stam rec, +30 max stam, 240s", "warn": "tremors + tunnel after"},
|
||||||
|
{"short": "Meldonin", "desc": "+0.5 stam rec, +Endurance 900s", "warn": "-hydration/-energy (minor)"},
|
||||||
|
{"short": "L1", "desc": "+30 max stam, +Strength, 120s", "warn": "-hydration/-energy"},
|
||||||
|
{"short": "SJ1", "desc": "+Endurance/Strength 180s", "warn": "-energy/-hydration after"},
|
||||||
|
{"short": "Adrenaline","desc": "Short burst +Endurance/Strength", "warn": "-Stress Resist"},
|
||||||
|
],
|
||||||
|
"skill": [
|
||||||
|
{"short": "Obdolbos 2", "desc": "All skills +20, weight +45%, 1800s", "warn": "-stam, -HP regen"},
|
||||||
|
{"short": "3-(b-TG)", "desc": "+Attention/Perception/Strength 240s", "warn": "tremors after"},
|
||||||
|
{"short": "SJ12", "desc": "+Perception 600s, body cool", "warn": "overheats at end"},
|
||||||
|
{"short": "2A2-(b-TG)", "desc": "+Attention/Perception, weight +15%, 900s", "warn": "-hydration"},
|
||||||
|
{"short": "Trimadol", "desc": "Broad skill buff + stam, 180s", "warn": "-energy/-hydration"},
|
||||||
|
],
|
||||||
|
"special": [
|
||||||
|
{"short": "SJ9", "desc": "Cools body -7°, 300s — hot map survival", "warn": "HP drain + tremors"},
|
||||||
|
{"short": "SJ12", "desc": "Cools body -4°, +Perception, 600s", "warn": "rebound heat after"},
|
||||||
|
{"short": "M.U.L.E.", "desc": "Weight limit +50% for 900s", "warn": "-HP regen"},
|
||||||
|
{"short": "Obdolbos","desc": "25% chance: all buffs + all debuffs", "warn": "may kill you"},
|
||||||
|
],
|
||||||
|
"risky": [
|
||||||
|
{"short": "Obdolbos", "desc": "25% chance everything fires at once", "warn": "may cause -600 HP"},
|
||||||
|
{"short": "PNB", "desc": "Fast HP/Strength burst then hard crash", "warn": "-Health/-Vitality 180s"},
|
||||||
|
{"short": "SJ9", "desc": "-HP regen whole duration, tremors", "warn": "don't use while injured"},
|
||||||
|
{"short": "Propital", "desc": "Tremors + tunnel vision at 270s delay", "warn": "plan ahead"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template("meds.html",
|
||||||
|
injectors=injectors,
|
||||||
|
skill_rows=skill_rows,
|
||||||
|
situations=situations)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5000, debug=True)
|
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||||
|
|||||||
@@ -295,6 +295,8 @@
|
|||||||
<a href="/quests">Quest Trees</a>
|
<a href="/quests">Quest Trees</a>
|
||||||
|
|
|
|
||||||
<a href="/loadout">Loadout Planner</a>
|
<a href="/loadout">Loadout Planner</a>
|
||||||
|
|
|
||||||
|
<a href="/meds">Injectors</a>
|
||||||
</nav>
|
</nav>
|
||||||
<h1>Collector Checklist</h1>
|
<h1>Collector Checklist</h1>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
|
|||||||
@@ -174,6 +174,8 @@
|
|||||||
<a href="/quests">Quest Trees</a>
|
<a href="/quests">Quest Trees</a>
|
||||||
|
|
|
|
||||||
<a href="/loadout">Loadout Planner</a>
|
<a href="/loadout">Loadout Planner</a>
|
||||||
|
|
|
||||||
|
<a href="/meds">Injectors</a>
|
||||||
</nav>
|
</nav>
|
||||||
<h1>OnlyScavs – Keys</h1>
|
<h1>OnlyScavs – Keys</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -223,7 +223,8 @@
|
|||||||
<div class="page">
|
<div class="page">
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/">← Keys</a> |
|
<a href="/">← Keys</a> |
|
||||||
<a href="/collector">Collector</a>
|
<a href="/collector">Collector</a> |
|
||||||
|
<a href="/meds">Injectors</a>
|
||||||
</nav>
|
</nav>
|
||||||
<h1>Loadout Planner</h1>
|
<h1>Loadout Planner</h1>
|
||||||
<p style="color:var(--muted);margin:0 0 16px;font-size:0.9rem;">
|
<p style="color:var(--muted);margin:0 0 16px;font-size:0.9rem;">
|
||||||
|
|||||||
574
templates/meds.html
Normal file
574
templates/meds.html
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnlyScavs – Injector Reference</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #121212;
|
||||||
|
--panel: #1a1a1a;
|
||||||
|
--panel2: #1e1e1e;
|
||||||
|
--text: #eee;
|
||||||
|
--muted: #888;
|
||||||
|
--border: #2a2a2a;
|
||||||
|
--accent: #9ccfff;
|
||||||
|
--good: #6ec96e;
|
||||||
|
--bad: #e06060;
|
||||||
|
--warn: #e0a040;
|
||||||
|
--neutral: #aaa;
|
||||||
|
--heal: #5db8a0;
|
||||||
|
--stam: #6abfdb;
|
||||||
|
--skill: #a58cf0;
|
||||||
|
--special: #c8a850;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.page { max-width: 1200px; margin: 0 auto; }
|
||||||
|
|
||||||
|
/* ── Nav ── */
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
nav a {
|
||||||
|
color: var(--muted);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
nav a:hover { color: var(--accent); border-color: var(--accent); }
|
||||||
|
nav a.active { color: var(--accent); border-color: var(--accent); background: #1a2533; }
|
||||||
|
|
||||||
|
h1 { font-size: 1.4rem; margin: 0 0 4px; }
|
||||||
|
.subtitle { color: var(--muted); font-size: 0.88rem; margin: 0 0 28px; }
|
||||||
|
|
||||||
|
/* ── Section headings ── */
|
||||||
|
.section-head {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding-bottom: 6px;
|
||||||
|
margin: 32px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ════════════════════════════════════
|
||||||
|
SITUATION GUIDE
|
||||||
|
════════════════════════════════════ */
|
||||||
|
.sit-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.sit-card {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
.sit-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.sit-label span.icon { font-size: 1rem; }
|
||||||
|
.sit-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.sit-item:last-child { border-bottom: none; }
|
||||||
|
.sit-name {
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 90px;
|
||||||
|
color: var(--accent);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.sit-desc { color: var(--muted); font-size: 0.8rem; flex: 1; }
|
||||||
|
.sit-warn { color: var(--bad); font-size: 0.75rem; white-space: nowrap; }
|
||||||
|
|
||||||
|
/* ════════════════════════════════════
|
||||||
|
COMPARISON GRID
|
||||||
|
════════════════════════════════════ */
|
||||||
|
.grid-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
table.comp {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 900px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
table.comp thead th {
|
||||||
|
background: var(--panel2);
|
||||||
|
padding: 10px 8px;
|
||||||
|
text-align: center;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
border-bottom: 2px solid var(--border);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
table.comp thead th:first-child {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 14px;
|
||||||
|
min-width: 160px;
|
||||||
|
background: #151515;
|
||||||
|
}
|
||||||
|
table.comp thead .th-icon {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 4px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
table.comp thead .th-name {
|
||||||
|
display: block;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
table.comp thead .th-short {
|
||||||
|
display: block;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
table.comp tbody tr { border-bottom: 1px solid var(--border); }
|
||||||
|
table.comp tbody tr:last-child { border-bottom: none; }
|
||||||
|
table.comp tbody tr:hover { background: rgba(255,255,255,0.03); }
|
||||||
|
table.comp tbody td {
|
||||||
|
padding: 7px 8px;
|
||||||
|
text-align: center;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
table.comp tbody td:first-child {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #151515;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
table.comp tbody tr.row-group td:first-child {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #555;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
table.comp tbody tr.row-group td {
|
||||||
|
background: #111;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* value cells */
|
||||||
|
.v-good { color: var(--good); font-weight: 600; }
|
||||||
|
.v-bad { color: var(--bad); }
|
||||||
|
.v-warn { color: var(--warn); }
|
||||||
|
.v-none { color: #383838; }
|
||||||
|
.v-heal { color: var(--heal); font-weight: 600; }
|
||||||
|
.v-stam { color: var(--stam); font-weight: 600; }
|
||||||
|
.v-skill { color: var(--skill); }
|
||||||
|
.v-special { color: var(--special); }
|
||||||
|
|
||||||
|
/* check / cross / dash symbols */
|
||||||
|
.sym-check { color: var(--good); font-size: 1rem; }
|
||||||
|
.sym-x { color: var(--bad); font-size: 1rem; }
|
||||||
|
.sym-dash { color: #333; }
|
||||||
|
|
||||||
|
/* duration pill */
|
||||||
|
.dur {
|
||||||
|
display: inline-block;
|
||||||
|
background: #252525;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 5px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.dur-long { background: #1e2a1e; color: var(--good); }
|
||||||
|
.dur-med { background: #252020; color: var(--warn); }
|
||||||
|
.dur-short { background: #2a1818; color: var(--bad); }
|
||||||
|
|
||||||
|
/* wiki link in header */
|
||||||
|
table.comp thead th a {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.65rem;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
table.comp thead th a:hover { color: var(--accent); }
|
||||||
|
|
||||||
|
/* ── filter tabs ── */
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.filter-btn {
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--muted);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.filter-btn:hover { color: var(--text); border-color: #555; }
|
||||||
|
.filter-btn.active { color: var(--accent); border-color: var(--accent); background: #1a2533; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<a href="/">Key Ratings</a>
|
||||||
|
<a href="/quests">Quest Tree</a>
|
||||||
|
<a href="/collector">Collector</a>
|
||||||
|
<a href="/loadout">Loadout</a>
|
||||||
|
<a href="/meds" class="active">Injectors</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>Injector Quick Reference</h1>
|
||||||
|
<p class="subtitle">All stim injectors — situation guide + full effect comparison</p>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════
|
||||||
|
SITUATION GUIDE
|
||||||
|
══════════════════════════════════ -->
|
||||||
|
<div class="section-head">Situation Guide — what to grab when</div>
|
||||||
|
<div class="sit-grid">
|
||||||
|
|
||||||
|
<div class="sit-card">
|
||||||
|
<div class="sit-label" style="color: var(--heal)"><span class="icon">🩸</span> Bleeding / Wound</div>
|
||||||
|
{% for inj in situations.bleed %}
|
||||||
|
<div class="sit-item">
|
||||||
|
<span class="sit-name">{{ inj.short }}</span>
|
||||||
|
<span class="sit-desc">{{ inj.desc }}</span>
|
||||||
|
{% if inj.warn %}<span class="sit-warn">⚠ {{ inj.warn }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sit-card">
|
||||||
|
<div class="sit-label" style="color: var(--heal)"><span class="icon">❤️</span> HP Regen</div>
|
||||||
|
{% for inj in situations.regen %}
|
||||||
|
<div class="sit-item">
|
||||||
|
<span class="sit-name">{{ inj.short }}</span>
|
||||||
|
<span class="sit-desc">{{ inj.desc }}</span>
|
||||||
|
{% if inj.warn %}<span class="sit-warn">⚠ {{ inj.warn }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sit-card">
|
||||||
|
<div class="sit-label" style="color: var(--stam)"><span class="icon">⚡</span> Stamina / Speed</div>
|
||||||
|
{% for inj in situations.stam %}
|
||||||
|
<div class="sit-item">
|
||||||
|
<span class="sit-name">{{ inj.short }}</span>
|
||||||
|
<span class="sit-desc">{{ inj.desc }}</span>
|
||||||
|
{% if inj.warn %}<span class="sit-warn">⚠ {{ inj.warn }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sit-card">
|
||||||
|
<div class="sit-label" style="color: var(--skill)"><span class="icon">💪</span> Skill Boosts</div>
|
||||||
|
{% for inj in situations.skill %}
|
||||||
|
<div class="sit-item">
|
||||||
|
<span class="sit-name">{{ inj.short }}</span>
|
||||||
|
<span class="sit-desc">{{ inj.desc }}</span>
|
||||||
|
{% if inj.warn %}<span class="sit-warn">⚠ {{ inj.warn }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sit-card">
|
||||||
|
<div class="sit-label" style="color: var(--special)"><span class="icon">🌡️</span> Temperature / Special</div>
|
||||||
|
{% for inj in situations.special %}
|
||||||
|
<div class="sit-item">
|
||||||
|
<span class="sit-name">{{ inj.short }}</span>
|
||||||
|
<span class="sit-desc">{{ inj.desc }}</span>
|
||||||
|
{% if inj.warn %}<span class="sit-warn">⚠ {{ inj.warn }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sit-card">
|
||||||
|
<div class="sit-label" style="color: var(--bad)"><span class="icon">☠️</span> High Risk / Gamble</div>
|
||||||
|
{% for inj in situations.risky %}
|
||||||
|
<div class="sit-item">
|
||||||
|
<span class="sit-name">{{ inj.short }}</span>
|
||||||
|
<span class="sit-desc">{{ inj.desc }}</span>
|
||||||
|
{% if inj.warn %}<span class="sit-warn">⚠ {{ inj.warn }}</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ══════════════════════════════════
|
||||||
|
COMPARISON GRID
|
||||||
|
══════════════════════════════════ -->
|
||||||
|
<div class="section-head" style="margin-top:40px">Full Comparison Grid</div>
|
||||||
|
|
||||||
|
<div class="filter-bar" id="filterBar">
|
||||||
|
<button class="filter-btn active" data-filter="all">All</button>
|
||||||
|
<button class="filter-btn" data-filter="heal">Healing</button>
|
||||||
|
<button class="filter-btn" data-filter="stam">Stamina</button>
|
||||||
|
<button class="filter-btn" data-filter="skill">Skill</button>
|
||||||
|
<button class="filter-btn" data-filter="special">Special</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-wrap">
|
||||||
|
<table class="comp" id="compTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Effect</th>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<th data-tags="{{ inj.tags }}">
|
||||||
|
{% if inj.icon %}<img class="th-icon" src="{{ inj.icon }}" alt="" onerror="this.style.display='none'">{% endif %}
|
||||||
|
<span class="th-name">{{ inj.short }}</span>
|
||||||
|
{% if inj.wiki %}<a href="{{ inj.wiki }}" target="_blank">wiki ↗</a>{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- HP REGENERATION -->
|
||||||
|
<tr class="row-group">
|
||||||
|
<td>Healing</td>
|
||||||
|
{% for inj in injectors %}<td></td>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>HP regen /s</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.hp_regen %}
|
||||||
|
<span class="v-heal">+{{ inj.hp_regen }}</span>
|
||||||
|
<span class="dur {% if inj.hp_regen_dur >= 300 %}dur-long{% elif inj.hp_regen_dur >= 60 %}dur-med{% else %}dur-short{% endif %}">{{ inj.hp_regen_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Stops bleeding</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.stops_bleed %}<span class="sym-check">✓</span>{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Antidote</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.antidote %}<span class="sym-check">✓</span>{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- STAMINA -->
|
||||||
|
<tr class="row-group">
|
||||||
|
<td>Stamina</td>
|
||||||
|
{% for inj in injectors %}<td></td>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Max stamina</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.max_stam %}
|
||||||
|
<span class="{% if inj.max_stam > 0 %}v-stam{% else %}v-bad{% endif %}">
|
||||||
|
{{ '+' if inj.max_stam > 0 else '' }}{{ inj.max_stam }}
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ inj.max_stam_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Stam recovery /s</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.stam_rec %}
|
||||||
|
<span class="{% if inj.stam_rec > 0 %}v-stam{% else %}v-bad{% endif %}">
|
||||||
|
{{ '+' if inj.stam_rec > 0 else '' }}{{ inj.stam_rec }}
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ inj.stam_rec_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Weight limit</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.weight %}
|
||||||
|
<span class="{% if inj.weight > 0 %}v-good{% else %}v-bad{% endif %}">
|
||||||
|
{{ '+' if inj.weight > 0 else '' }}{{ (inj.weight * 100)|int }}%
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ inj.weight_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- SKILLS -->
|
||||||
|
<tr class="row-group">
|
||||||
|
<td>Skill Buffs</td>
|
||||||
|
{% for inj in injectors %}<td></td>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% for skill_name in skill_rows %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ skill_name }}</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% set val = inj.skills.get(skill_name) %}
|
||||||
|
{% if val %}
|
||||||
|
<span class="{% if val.value > 0 %}v-skill{% else %}v-bad{% endif %}">
|
||||||
|
{{ '+' if val.value > 0 else '' }}{{ val.value }}
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ val.duration }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- SPECIAL -->
|
||||||
|
<tr class="row-group">
|
||||||
|
<td>Special</td>
|
||||||
|
{% for inj in injectors %}<td></td>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Body temp Δ</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.body_temp %}
|
||||||
|
<span class="{% if inj.body_temp > 0 %}v-bad{% else %}v-good{% endif %}">
|
||||||
|
{{ '+' if inj.body_temp > 0 else '' }}{{ inj.body_temp }}°
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ inj.body_temp_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Energy Δ /s</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.energy %}
|
||||||
|
<span class="{% if inj.energy > 0 %}v-good{% else %}v-bad{% endif %}">
|
||||||
|
{{ '+' if inj.energy > 0 else '' }}{{ inj.energy }}
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ inj.energy_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Hydration Δ /s</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.hydration %}
|
||||||
|
<span class="{% if inj.hydration > 0 %}v-good{% else %}v-bad{% endif %}">
|
||||||
|
{{ '+' if inj.hydration > 0 else '' }}{{ inj.hydration }}
|
||||||
|
</span>
|
||||||
|
<span class="dur">{{ inj.hydration_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- SIDE EFFECTS -->
|
||||||
|
<tr class="row-group">
|
||||||
|
<td>Side Effects</td>
|
||||||
|
{% for inj in injectors %}<td></td>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tremors</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.tremor %}
|
||||||
|
<span class="v-bad">⚠</span>
|
||||||
|
<span class="dur dur-short">{{ inj.tremor_delay }}s delay · {{ inj.tremor_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tunnel vision</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.tunnel %}
|
||||||
|
<span class="v-bad">⚠</span>
|
||||||
|
<span class="dur dur-short">{{ inj.tunnel_delay }}s delay · {{ inj.tunnel_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Pain</td>
|
||||||
|
{% for inj in injectors %}
|
||||||
|
<td>
|
||||||
|
{% if inj.pain %}
|
||||||
|
<span class="v-bad">⚠</span>
|
||||||
|
<span class="dur dur-short">{{ inj.pain_delay }}s delay · {{ inj.pain_dur }}s</span>
|
||||||
|
{% else %}<span class="sym-dash">—</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div><!-- /grid-wrap -->
|
||||||
|
|
||||||
|
</div><!-- /page -->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Filter columns by tag
|
||||||
|
document.getElementById("filterBar").addEventListener("click", function(e) {
|
||||||
|
const btn = e.target.closest(".filter-btn");
|
||||||
|
if (!btn) return;
|
||||||
|
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
|
||||||
|
btn.classList.add("active");
|
||||||
|
const filter = btn.dataset.filter;
|
||||||
|
const table = document.getElementById("compTable");
|
||||||
|
const headers = table.querySelectorAll("thead th");
|
||||||
|
headers.forEach((th, i) => {
|
||||||
|
if (i === 0) return; // label col
|
||||||
|
const tags = (th.dataset.tags || "").split(",");
|
||||||
|
const show = filter === "all" || tags.includes(filter);
|
||||||
|
// toggle all cells in column i
|
||||||
|
table.querySelectorAll("tr").forEach(tr => {
|
||||||
|
const cells = tr.children;
|
||||||
|
if (cells[i]) cells[i].style.display = show ? "" : "none";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -232,6 +232,8 @@
|
|||||||
<a href="/collector">Collector Checklist</a>
|
<a href="/collector">Collector Checklist</a>
|
||||||
|
|
|
|
||||||
<a href="/loadout">Loadout Planner</a>
|
<a href="/loadout">Loadout Planner</a>
|
||||||
|
|
|
||||||
|
<a href="/meds">Injectors</a>
|
||||||
</nav>
|
</nav>
|
||||||
<h1>Quest Trees</h1>
|
<h1>Quest Trees</h1>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user