Files
onlyscavs/templates/barters.html
2026-03-26 05:04:13 +00:00

399 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html>
<head>
<title>OnlyScavs Barter Calculator</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;
--accent2: #ffd580;
--good: #6ec96e;
--bad: #e06060;
--warn: #e0a040;
}
* { box-sizing: border-box; }
body {
font-family: sans-serif;
background: var(--bg);
color: var(--text);
margin: 0;
padding: 16px;
}
.page { max-width: 1100px; margin: 0 auto; }
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 20px; }
/* ── Filters ── */
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
margin-bottom: 20px;
}
.filters input[type=text] {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
padding: 6px 10px;
font-size: 0.88rem;
width: 220px;
}
.filters input[type=text]:focus { outline: none; border-color: var(--accent); }
.filters select {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
padding: 6px 10px;
font-size: 0.88rem;
}
.filters label { color: var(--muted); font-size: 0.82rem; }
/* ── Barter Cards ── */
.barter-list { display: flex; flex-direction: column; gap: 12px; }
.barter-card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 10px;
padding: 16px 18px;
}
.barter-card.task-locked {
border-color: #3a3020;
}
.barter-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.reward-icon {
width: 40px;
height: 40px;
object-fit: contain;
background: #111;
border-radius: 4px;
border: 1px solid var(--border);
flex-shrink: 0;
}
.reward-info { flex: 1; min-width: 0; }
.reward-name {
font-size: 1rem;
font-weight: 700;
color: var(--accent);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.reward-name a { color: inherit; text-decoration: none; }
.reward-name a:hover { text-decoration: underline; }
.reward-meta {
font-size: 0.78rem;
color: var(--muted);
margin-top: 2px;
}
.trader-badge {
font-size: 0.75rem;
font-weight: 700;
padding: 2px 8px;
border-radius: 99px;
background: #1e2a1e;
color: var(--good);
border: 1px solid #2a3e2a;
white-space: nowrap;
}
.task-badge {
font-size: 0.72rem;
padding: 2px 8px;
border-radius: 99px;
background: #2a2010;
color: var(--warn);
border: 1px solid #3a3020;
white-space: nowrap;
}
/* ── Required items ── */
.required-label {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 8px;
}
.required-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.req-item {
display: flex;
align-items: center;
gap: 6px;
background: var(--panel2);
border: 1px solid var(--border);
border-radius: 6px;
padding: 6px 10px;
min-width: 160px;
}
.req-icon {
width: 30px;
height: 30px;
object-fit: contain;
background: #111;
border-radius: 3px;
flex-shrink: 0;
}
.req-icon-placeholder {
width: 30px;
height: 30px;
background: #222;
border-radius: 3px;
flex-shrink: 0;
}
.req-text { flex: 1; min-width: 0; }
.req-name {
font-size: 0.82rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.req-count { font-size: 0.75rem; color: var(--muted); }
.req-price-wrap {
display: flex;
align-items: center;
gap: 4px;
}
.req-price-input {
width: 90px;
background: #111;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text);
font-size: 0.82rem;
padding: 3px 6px;
text-align: right;
}
.req-price-input:focus { outline: none; border-color: var(--accent); }
.req-price-unit { font-size: 0.72rem; color: var(--muted); }
/* ── Total cost row ── */
.total-row {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
padding-top: 10px;
border-top: 1px solid var(--border);
}
.total-label { font-size: 0.82rem; color: var(--muted); }
.total-cost {
font-size: 1.1rem;
font-weight: 700;
color: var(--accent2);
font-variant-numeric: tabular-nums;
}
.total-hint { font-size: 0.75rem; color: var(--muted); }
/* ── Empty state ── */
.empty { color: var(--muted); text-align: center; padding: 48px 0; font-size: 0.9rem; }
</style>
</head>
<body>
<div class="page">
<nav>
<a href="/">Home</a>
<a href="/keys">Keys</a>
<a href="/collector">Collector</a>
<a href="/quests">Quests</a>
<a href="/loadout">Loadout</a>
<a href="/meds">Injectors</a>
<a href="/barters" class="active">Barters</a>
</nav>
<h1>Barter Calculator</h1>
<p class="subtitle">Enter flea market prices for required items to see the total rouble cost of any barter.</p>
<div class="filters">
<div>
<label>Search</label><br>
<input type="text" id="search" placeholder="item name, trader…" oninput="applyFilters()">
</div>
<div>
<label>Trader</label><br>
<select id="traderFilter" onchange="applyFilters()">
<option value="">All traders</option>
{% set traders = barters | map(attribute='trader') | unique | sort %}
{% for t in traders %}
<option value="{{ t }}">{{ t }}</option>
{% endfor %}
</select>
</div>
<div>
<label>LL (min)</label><br>
<select id="llFilter" onchange="applyFilters()">
<option value="0">Any</option>
<option value="1">LL1+</option>
<option value="2">LL2+</option>
<option value="3">LL3+</option>
<option value="4">LL4</option>
</select>
</div>
<div>
<label>Task locked</label><br>
<select id="taskFilter" onchange="applyFilters()">
<option value="">All</option>
<option value="no">No task required</option>
<option value="yes">Task required</option>
</select>
</div>
</div>
{% if barters %}
<div class="barter-list" id="barterList">
{% for b in barters %}
<div class="barter-card{% if b.task_unlock %} task-locked{% endif %}"
data-trader="{{ b.trader }}"
data-level="{{ b.level }}"
data-task="{{ 'yes' if b.task_unlock else 'no' }}"
data-search="{{ (b.reward_name ~ ' ' ~ b.trader ~ ' ' ~ (b.required | map(attribute='name') | join(' '))) | lower }}">
<div class="barter-header">
{% if b.reward_icon %}
<img class="reward-icon" src="{{ b.reward_icon }}" alt="{{ b.reward_short }}" loading="lazy">
{% endif %}
<div class="reward-info">
<div class="reward-name">
{% if b.reward_wiki %}
<a href="{{ b.reward_wiki }}" target="_blank" rel="noopener">{{ b.reward_name }}{% if b.reward_count > 1 %} ×{{ b.reward_count }}{% endif %}</a>
{% else %}
{{ b.reward_name }}{% if b.reward_count > 1 %} ×{{ b.reward_count }}{% endif %}
{% endif %}
</div>
<div class="reward-meta">{{ b.trader }} · LL{{ b.level }}</div>
</div>
<span class="trader-badge">{{ b.trader }} LL{{ b.level }}</span>
{% if b.task_unlock %}
<span class="task-badge" title="Requires task: {{ b.task_unlock }}">🔒 {{ b.task_unlock }}</span>
{% endif %}
</div>
<div class="required-label">Required items</div>
<div class="required-items">
{% for ri in b.required %}
<div class="req-item">
{% if ri.icon %}
<img class="req-icon" src="{{ ri.icon }}" alt="{{ ri.short }}" loading="lazy">
{% else %}
<div class="req-icon-placeholder"></div>
{% endif %}
<div class="req-text">
<div class="req-name" title="{{ ri.name }}">{{ ri.name }}</div>
<div class="req-count">× {{ ri.count }}</div>
</div>
<div class="req-price-wrap">
<input class="req-price-input"
type="number"
min="0"
step="1"
placeholder="price"
title="Price per unit in roubles"
data-count="{{ ri.count }}"
oninput="recalc(this)">
<span class="req-price-unit"></span>
</div>
</div>
{% endfor %}
</div>
<div class="total-row">
<span class="total-label">Total cost:</span>
<span class="total-cost" data-total="0"></span>
<span class="total-hint">Enter prices above to calculate</span>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty">No barter data available. Check your connection to tarkov.dev.</div>
{% endif %}
</div>
<script>
// Recalculate total cost for a barter card when any price input changes
function recalc(input) {
const card = input.closest('.barter-card');
const inputs = card.querySelectorAll('.req-price-input');
let total = 0;
let allFilled = true;
inputs.forEach(inp => {
const price = parseFloat(inp.value) || 0;
const count = parseInt(inp.dataset.count, 10) || 1;
if (inp.value.trim() === '') allFilled = false;
total += price * count;
});
const totalEl = card.querySelector('.total-cost');
const hintEl = card.querySelector('.total-hint');
if (total > 0) {
totalEl.textContent = total.toLocaleString() + ' ₽';
hintEl.textContent = allFilled ? 'all items priced' : 'some items unpriced';
} else {
totalEl.textContent = '—';
hintEl.textContent = 'Enter prices above to calculate';
}
}
// Filter cards by search text, trader, level, and task lock
function applyFilters() {
const search = document.getElementById('search').value.toLowerCase();
const trader = document.getElementById('traderFilter').value;
const ll = parseInt(document.getElementById('llFilter').value, 10) || 0;
const task = document.getElementById('taskFilter').value;
document.querySelectorAll('.barter-card').forEach(card => {
const matchSearch = !search || card.dataset.search.includes(search);
const matchTrader = !trader || card.dataset.trader === trader;
const matchLL = !ll || parseInt(card.dataset.level, 10) >= ll;
const matchTask = !task || card.dataset.task === task;
card.style.display = (matchSearch && matchTrader && matchLL && matchTask) ? '' : 'none';
});
}
</script>
</body>
</html>