@@ -0,0 +1,398 @@
<!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 : 16 px ;
}
. page { max-width : 1100 px ; margin : 0 auto ; }
nav {
display : flex ;
gap : 12 px ;
flex-wrap : wrap ;
margin-bottom : 24 px ;
font-size : 0.88 rem ;
}
nav a {
color : var ( - - muted ) ;
text-decoration : none ;
padding : 4 px 10 px ;
border : 1 px solid var ( - - border ) ;
border-radius : 4 px ;
}
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.4 rem ; margin : 0 0 4 px ; }
. subtitle { color : var ( - - muted ) ; font-size : 0.88 rem ; margin : 0 0 20 px ; }
/* ── Filters ── */
. filters {
display : flex ;
gap : 10 px ;
flex-wrap : wrap ;
align-items : center ;
margin-bottom : 20 px ;
}
. filters input [ type = text ] {
background : var ( - - panel ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 6 px ;
color : var ( - - text ) ;
padding : 6 px 10 px ;
font-size : 0.88 rem ;
width : 220 px ;
}
. filters input [ type = text ] : focus { outline : none ; border-color : var ( - - accent ) ; }
. filters select {
background : var ( - - panel ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 6 px ;
color : var ( - - text ) ;
padding : 6 px 10 px ;
font-size : 0.88 rem ;
}
. filters label { color : var ( - - muted ) ; font-size : 0.82 rem ; }
/* ── Barter Cards ── */
. barter-list { display : flex ; flex-direction : column ; gap : 12 px ; }
. barter-card {
background : var ( - - panel ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 10 px ;
padding : 16 px 18 px ;
}
. barter-card . task-locked {
border-color : #3a3020 ;
}
. barter-header {
display : flex ;
align-items : center ;
gap : 12 px ;
margin-bottom : 12 px ;
flex-wrap : wrap ;
}
. reward-icon {
width : 40 px ;
height : 40 px ;
object-fit : contain ;
background : #111 ;
border-radius : 4 px ;
border : 1 px solid var ( - - border ) ;
flex-shrink : 0 ;
}
. reward-info { flex : 1 ; min-width : 0 ; }
. reward-name {
font-size : 1 rem ;
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.78 rem ;
color : var ( - - muted ) ;
margin-top : 2 px ;
}
. trader-badge {
font-size : 0.75 rem ;
font-weight : 700 ;
padding : 2 px 8 px ;
border-radius : 99 px ;
background : #1e2a1e ;
color : var ( - - good ) ;
border : 1 px solid #2a3e2a ;
white-space : nowrap ;
}
. task-badge {
font-size : 0.72 rem ;
padding : 2 px 8 px ;
border-radius : 99 px ;
background : #2a2010 ;
color : var ( - - warn ) ;
border : 1 px solid #3a3020 ;
white-space : nowrap ;
}
/* ── Required items ── */
. required-label {
font-size : 0.72 rem ;
font-weight : 700 ;
letter-spacing : 0.1 em ;
text-transform : uppercase ;
color : var ( - - muted ) ;
margin-bottom : 8 px ;
}
. required-items {
display : flex ;
flex-wrap : wrap ;
gap : 8 px ;
margin-bottom : 14 px ;
}
. req-item {
display : flex ;
align-items : center ;
gap : 6 px ;
background : var ( - - panel2 ) ;
border : 1 px solid var ( - - border ) ;
border-radius : 6 px ;
padding : 6 px 10 px ;
min-width : 160 px ;
}
. req-icon {
width : 30 px ;
height : 30 px ;
object-fit : contain ;
background : #111 ;
border-radius : 3 px ;
flex-shrink : 0 ;
}
. req-icon-placeholder {
width : 30 px ;
height : 30 px ;
background : #222 ;
border-radius : 3 px ;
flex-shrink : 0 ;
}
. req-text { flex : 1 ; min-width : 0 ; }
. req-name {
font-size : 0.82 rem ;
font-weight : 600 ;
white-space : nowrap ;
overflow : hidden ;
text-overflow : ellipsis ;
}
. req-count { font-size : 0.75 rem ; color : var ( - - muted ) ; }
. req-price-wrap {
display : flex ;
align-items : center ;
gap : 4 px ;
}
. req-price-input {
width : 90 px ;
background : #111 ;
border : 1 px solid var ( - - border ) ;
border-radius : 4 px ;
color : var ( - - text ) ;
font-size : 0.82 rem ;
padding : 3 px 6 px ;
text-align : right ;
}
. req-price-input : focus { outline : none ; border-color : var ( - - accent ) ; }
. req-price-unit { font-size : 0.72 rem ; color : var ( - - muted ) ; }
/* ── Total cost row ── */
. total-row {
display : flex ;
align-items : center ;
gap : 16 px ;
flex-wrap : wrap ;
padding-top : 10 px ;
border-top : 1 px solid var ( - - border ) ;
}
. total-label { font-size : 0.82 rem ; color : var ( - - muted ) ; }
. total-cost {
font-size : 1.1 rem ;
font-weight : 700 ;
color : var ( - - accent2 ) ;
font-variant-numeric : tabular-nums ;
}
. total-hint { font-size : 0.75 rem ; color : var ( - - muted ) ; }
/* ── Empty state ── */
. empty { color : var ( - - muted ) ; text-align : center ; padding : 48 px 0 ; font-size : 0.9 rem ; }
< / 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 >