399 lines
12 KiB
HTML
399 lines
12 KiB
HTML
<!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>
|