<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>The Paramount Exchange</title>
<style>
html { overflow-y: scroll; }
body {
font-family: Inter, Arial, sans-serif;
background-color: #0f172a;
color: #e5e7eb;
padding: 24px;
}
h1 {
margin-bottom: 18px;
font-size: 30px;
font-weight: 800;
border-bottom: 2px solid #3b82f6;
display: inline-block;
padding-bottom: 6px;
color: #f1f5f9;
}
.panel {
background-color: #1e293b;
border-radius: 12px;
padding: 16px;
margin-bottom: 18px;
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
contain: layout;
position: relative;
}
.controls,
.sort-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.controls > * { flex: 1 1 200px; }
input,
button {
background-color: #1e293b;
color: #f1f5f9;
border: 1px solid #334155;
border-radius: 8px;
padding: 8px 12px;
font-size: 14px;
cursor: pointer;
}
input { cursor: text; }
button:hover { background-color: #273449; }
button.active { border-color: #3b82f6; color: #3b82f6; }
/* ===== DROPDOWN ===== */
.dropdown { position: relative; }
.dropdown-btn {
width: 100%;
text-align: left;
padding-right: 32px;
position: relative;
}
.dropdown-btn::after {
content: "";
position: absolute;
right: 12px;
top: 50%;
width: 8px;
height: 8px;
border-right: 2px solid #94a3b8;
border-bottom: 2px solid #94a3b8;
transform: translateY(-60%) rotate(45deg);
transition: transform 0.25s ease;
pointer-events: none;
}
.dropdown.open .dropdown-btn::after {
transform: translateY(-40%) rotate(-135deg);
}
.dropdown-menu {
position: absolute;
z-index: 1000;
top: calc(100% + 6px);
left: 0;
right: 0;
background: #0f172a;
border: 1px solid #334155;
border-radius: 10px;
padding: 6px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.dropdown.open .dropdown-menu {
opacity: 1;
pointer-events: auto;
}
.dropdown-menu label {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: 6px;
cursor: pointer;
}
.dropdown-menu label:hover {
background: #273449;
}
/* ===== BOOKMARK ICON ===== */
.bookmark-btn {
background: none;
border: none;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
transform: translateY(2px);
}
.bookmark-btn svg {
width: 16px;
height: 16px;
stroke: #94a3b8;
fill: none;
stroke-width: 2;
}
.bookmark-btn.active svg {
fill: #3b82f6;
stroke: #3b82f6;
}
/* ===== SORT ===== */
.sort-row {
border-top: 1px solid #334155;
padding-top: 10px;
}
.sort-btn {
position: relative;
padding-right: 24px;
}
.sort-btn::after {
content: '';
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
border: 6px solid transparent;
display: none;
}
.sort-btn.active::after { display: inline-block; }
.sort-btn.active.asc::after { border-bottom-color: #3b82f6; }
.sort-btn.active.desc::after { border-top-color: #3b82f6; }
/* ===== TABLE ===== */
.table-wrapper {
background-color: #1e293b;
border-radius: 12px;
padding: 12px;
}
table {
width: 100%;
border-collapse: separate;
border-spacing: 0 10px;
}
thead th {
background: #334155;
padding: 10px;
}
tbody tr {
background: #1e293b;
transition: background 0.2s ease;
}
tbody tr:nth-child(even) { background: #273449; }
tbody tr:hover { background: #334155; }
td {
padding: 10px;
text-align: center;
vertical-align: middle;
}
td:first-child {
width: 80px;
padding: 0;
}
.item-image {
width: 100%;
height: 56px;
object-fit: contain;
}
/* ===== ITEM NAME CELL ===== */
.item-name-cell {
display: flex;
align-items: center;
gap: 8px;
}
/* ===== RARITY BADGES ===== */
.badge {
padding: 3px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.Common { background:#475569; }
.Uncommon { background:#eab308; color:#0f172a; }
.Rare { background:#6366f1; }
.Limited { background:#a855f7; }
.Mystic { background:#f43f5e; }
.Legendary { background:#22c55e; color:#0f172a; }
/* ===== BOOKMARK PANEL ===== */
.bookmark-panel {
background: #1e293b;
border-radius: 12px;
padding: 12px;
margin-bottom: 18px;
}
.bookmark-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background: #273449;
border-radius: 8px;
margin-bottom: 6px;
cursor: pointer;
}
.bookmark-item:hover {
background: #334155;
}
/* ===== BOOKMARK ITEM NAME AS PILL ===== */
.bookmark-name-pill {
padding: 3px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.bookmark-name-pill.Common { background:#475569; }
.bookmark-name-pill.Uncommon { background:#eab308; color:#0f172a; }
.bookmark-name-pill.Rare { background:#6366f1; }
.bookmark-name-pill.Limited { background:#a855f7; }
.bookmark-name-pill.Mystic { background:#f43f5e; }
.bookmark-name-pill.Legendary { background:#22c55e; color:#0f172a; }
</style>
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-RP9RH32TCH"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-RP9RH32TCH');
</script>
</head>
<body>
<h1>The Paramount Exchange</h1>
<div class="panel">
<div class="controls">
<input id="searchInput" placeholder="Search items…">
<!-- CATEGORY DROPDOWN -->
<div class="dropdown" id="categorySelect">
<button class="dropdown-btn" id="categoryBtn">All Categories</button>
<div class="dropdown-menu" id="categoryMenu">
<label data-value="">All Categories</label>
<label data-value="Weapon">Weapon</label>
<label data-value="Equipment">Equipment</label>
<label data-value="Scroll">Scroll</label>
<label data-value="Ingredient">Ingredient</label>
<label data-value="Items">Items</label>
</div>
</div>
<!-- RARITY DROPDOWN -->
<div class="dropdown" id="raritySelect">
<button class="dropdown-btn" id="rarityBtn">Rarities</button>
<div class="dropdown-menu" id="rarityMenu">
<label><input type="checkbox" value="Legendary"> Legendary</label>
<label><input type="checkbox" value="Mystic"> Mystic</label>
<label><input type="checkbox" value="Limited"> Limited</label>
<label><input type="checkbox" value="Rare"> Rare</label>
<label><input type="checkbox" value="Uncommon"> Uncommon</label>
<label><input type="checkbox" value="Common"> Common</label>
</div>
</div>
<button id="bossToggle">Boss Drop</button>
<button id="sunkenToggle">Sunken</button>
<button id="unobtainableToggle">Unobtainable</button>
</div>
<div class="sort-row">
<button class="sort-btn active asc" data-sort="name">Name</button>
<button class="sort-btn" data-sort="value">Value</button>
<button class="sort-btn" data-sort="rarity">Rarity</button>
</div>
</div>
<div class="bookmark-panel">
<strong>Bookmarked Items (click to remove)</strong>
<div id="bookmarkList"></div>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Image</th><th>Item</th><th>Category</th><th>Rarity</th><th>Value</th>
</tr>
</thead>
<tbody id="itemTable"></tbody>
</table>
</div>
<div id="chartModal" style="
position:fixed;
inset:0;
background:rgba(0,0,0,.6);
display:none;
align-items:center;
justify-content:center;
z-index:2000;
">
<div style="
background:#1e293b;
padding:20px;
border-radius:14px;
width:90%;
max-width:700px;
">
<div style="display:flex;justify-content:space-between;align-items:center">
<h3 id="chartTitle"></h3>
<button onclick="closeChart()">✕</button>
</div>
<div style="display:flex;gap:8px;margin-bottom:12px">
<button onclick="setRange(7)">7D</button>
<button onclick="setRange(30)">30D</button>
<button onclick="setRange(90)">90D</button>
<button onclick="setRange(365)">1Y</button>
</div>
<canvas id="historyChart" height="260"></canvas>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
function trackItemView(itemName) {
gtag('event', 'view_item', {
item_name: itemName,
event_category: 'Items',
event_label: itemName
});
}
const state = {
search: "",
category: "",
rarities: new Set(),
sort: "name",
asc: true,
boss: false,
sunken: false,
unobtainable: false
};
const rarityRank = { Common:1, Uncommon:2, Rare:3, Limited:4, Mystic:5, Legendary:6 };
let items = [];
const table = document.getElementById("itemTable");
const searchInput = document.getElementById("searchInput");
const bookmarks = new Set(JSON.parse(localStorage.getItem("bookmarks") || "[]"));
const saveBookmarks = () => localStorage.setItem("bookmarks", JSON.stringify([...bookmarks]));
/* ===== DROPDOWN GLOBAL CLOSE ===== */
document.addEventListener("click", e => {
document.querySelectorAll(".dropdown").forEach(d => {
if (!d.contains(e.target)) d.classList.remove("open");
});
});
/* ===== CATEGORY DROPDOWN ===== */
const catWrap = document.getElementById("categorySelect");
const catBtn = document.getElementById("categoryBtn");
const catMenu = document.getElementById("categoryMenu");
catBtn.onclick = e => {
e.stopPropagation();
catWrap.classList.toggle("open");
};
catMenu.querySelectorAll("label").forEach(l => {
l.onclick = () => {
state.category = l.dataset.value;
catBtn.textContent = l.textContent;
catWrap.classList.remove("open");
renderItems();
};
});
/* ===== RARITY DROPDOWN ===== */
const rarWrap = document.getElementById("raritySelect");
const rarBtn = document.getElementById("rarityBtn");
const rarMenu = document.getElementById("rarityMenu");
rarBtn.onclick = e => {
e.stopPropagation();
rarWrap.classList.toggle("open");
};
rarMenu.querySelectorAll("input").forEach(cb => {
cb.onchange = () => {
cb.checked ? state.rarities.add(cb.value) : state.rarities.delete(cb.value);
rarBtn.textContent = state.rarities.size ? `Rarities (${state.rarities.size})` : "Rarities";
renderItems();
};
});
/* ===== RENDER ===== */
function toggleBookmark(name) {
bookmarks.has(name) ? bookmarks.delete(name) : bookmarks.add(name);
saveBookmarks();
renderItems();
renderBookmarks();
}
function renderItems() {
table.innerHTML = "";
let filtered = items.filter(i =>
i.name.toLowerCase().includes(state.search) &&
(!state.category || i.category === state.category) &&
(!state.rarities.size || state.rarities.has(i.rarity)) &&
(!state.boss || i.bossDrop) &&
(!state.sunken || i.sunken) &&
(!state.unobtainable || i.unobtainable)
);
filtered.sort((a,b)=>{
let r =
state.sort==="value" ? a.value-b.value :
state.sort==="rarity" ? rarityRank[a.rarity]-rarityRank[b.rarity] :
a.name.localeCompare(b.name);
return state.asc ? r : -r;
});
if (!filtered.length) {
table.innerHTML = `<tr><td colspan="5" style="padding:20px;opacity:.6;">No items found</td></tr>`;
return;
}
filtered.forEach(i => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td><img class="item-image" src="${i.image||''}" onerror="this.src='https://via.placeholder.com/64?text=?'"></td>
<td style="text-align:left">
<div class="item-name-cell">
<button class="bookmark-btn ${bookmarks.has(i.name) ? "active" : ""}"
onclick="toggleBookmark('${i.name.replace(/'/g,"\\'")}')">
<svg viewBox="0 0 24 24">
<path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z"/>
</svg>
</button>
<span style="cursor:pointer;color:#60a5fa"
onclick="openChart('${i.name.replace(/'/g,"\\'")}')">
${i.name}
</span>
</div>
</td>
<td>${i.category}</td>
<td><span class="badge ${i.rarity}">${i.rarity}</span></td>
<td>${i.value.toLocaleString()}</td>
`;
table.appendChild(tr);
});
}
function renderBookmarks() {
const wrap = document.getElementById("bookmarkList");
wrap.innerHTML = "";
const list = items.filter(i => bookmarks.has(i.name))
.sort((a,b)=>b.value-a.value);
if (!list.length) {
wrap.innerHTML = `<div style="opacity:.6;">No bookmarks yet</div>`;
return;
}
list.forEach(i => {
const div = document.createElement("div");
div.className = "bookmark-item";
div.onclick = () => toggleBookmark(i.name);
div.innerHTML = `
<span class="bookmark-name-pill ${i.rarity}">${i.name}</span>
<strong>${i.value.toLocaleString()}</strong>
`;
wrap.appendChild(div);
});
}
/* ===== EVENTS ===== */
let t;
searchInput.oninput = e => {
clearTimeout(t);
t = setTimeout(()=>{ state.search = e.target.value.toLowerCase(); renderItems(); },200);
};
["boss","sunken","unobtainable"].forEach(k=>{
const b=document.getElementById(k+"Toggle");
b.onclick=()=>{
state[k]=!state[k];
b.classList.toggle("active",state[k]);
renderItems();
};
});
document.querySelectorAll(".sort-btn").forEach(btn=>{
btn.onclick=()=>{
const s=btn.dataset.sort;
state.asc = state.sort===s ? !state.asc : true;
state.sort = s;
document.querySelectorAll(".sort-btn").forEach(b=>b.classList.remove("active","asc","desc"));
btn.classList.add("active",state.asc?"asc":"desc");
renderItems();
};
});
/* ===== LOAD DATA ===== */
fetch("https://opensheet.elk.sh/1jwgNgbyvErRNKduisvOkzX_zZYFAilWIiAC0nrmw2ZE/Sheet1")
.then(r=>r.json())
.then(d=>{
items = d.map(i=>({
...i,
value:+i.value || 0,
bossDrop:i.bossDrop==="TRUE",
sunken:i.sunken==="TRUE",
unobtainable:i.unobtainable==="TRUE"
}));
renderItems();
renderBookmarks();
})
.catch(()=>{ table.innerHTML = `<tr><td colspan="5" style="padding:20px;opacity:.6;">Failed to load items</td></tr>` });
/* ===== HISTORY CHART ===== */
let history = [];
let chart;
let currentItem = "";
fetch("https://opensheet.elk.sh/1jwgNgbyvErRNKduisvOkzX_zZYFAilWIiAC0nrmw2ZE/History")
.then(r => r.json())
.then(d => history = d.map(h => ({
...h,
value: +h.value,
date: new Date(h.date + "T00:00:00Z")
})));
function openChart(name) {
trackItemView(name);
currentItem = name;
document.getElementById("chartTitle").textContent = name;
document.getElementById("chartModal").style.display = "flex";
setRange(30);
}
function closeChart() {
document.getElementById("chartModal").style.display = "none";
chart?.destroy();
}
function setRange(days) {
const now = new Date();
const cutoff = new Date(Date.UTC(
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate() - days
));
const data = history
.filter(h =>
h.name?.trim().toLowerCase() === currentItem.trim().toLowerCase() &&
h.date instanceof Date &&
!isNaN(h.date) &&
h.date >= cutoff
)
.sort((a, b) => a.date - b.date);
const ctx = document.getElementById("historyChart");
chart?.destroy();
chart = new Chart(ctx, {
type: "line",
data: {
labels: data.map(d =>
d.date.toLocaleDateString(undefined, { timeZone: "UTC" })
),
datasets: [{
data: data.map(d => d.value),
borderWidth: 2,
tension: 0.35
}]
},
options: {
plugins: { legend: { display: false } },
scales: {
y: {
ticks: { callback: v => v.toLocaleString() }
}
}
}
});
}
</script>
</body>
</html>