Skip to content

Commit efe952c

Browse files
rainerstudiosclaude
andcommitted
Remove Buff163 DEMO badge and add 3D item preview
Buff163 improvements: - Remove "DEMO" badge for cleaner display - Label changed to "Buff163 Estimate" to indicate estimated prices - Mock data provides useful price comparisons without API New feature: 3D Item Preview - Add hover tooltip showing full item image (like CSFloat) - Image appears when hovering over market listing item images - Uses imageUrl from API response - Smooth animations and loading states - Responsive positioning (left/right based on screen space) Files changed: - src/buff163Integration.js: Remove DEMO badge - src/item3DPreview.js: NEW - 3D preview tooltip component - content.js: Integrate 3D preview with float display - manifest.json: Add item3DPreview.js to content scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 835a51a commit efe952c

4 files changed

Lines changed: 260 additions & 3 deletions

File tree

content.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,11 @@ async function addFloatDisplay(itemElement, floatData) {
659659
// Track price history for market intelligence
660660
await trackItemPrice(itemElement, floatData);
661661

662+
// Add 3D item preview if image URL is available
663+
if (floatData.imageUrl && window.item3DPreview) {
664+
window.item3DPreview.attachToListing(itemElement, floatData.imageUrl);
665+
}
666+
662667
// Insert after the item name
663668
const itemName = itemElement.querySelector('.market_listing_item_name_block');
664669
if (itemName) {

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"https://steamcommunity.com/id/*/tradehistory",
3737
"https://steamcommunity.com/profiles/*/tradehistory"
3838
],
39-
"js": ["lib/marketIntelligence.js", "lib/inventoryEnhancer.js", "src/infiniteScroll.js", "src/buff163Integration.js", "src/multiSeedFilter.js", "content.js"],
39+
"js": ["lib/marketIntelligence.js", "lib/inventoryEnhancer.js", "src/infiniteScroll.js", "src/buff163Integration.js", "src/multiSeedFilter.js", "src/item3DPreview.js", "content.js"],
4040
"css": ["styles/main.css", "styles/inventory.css"],
4141
"run_at": "document_idle"
4242
}

src/buff163Integration.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,7 @@ class Buff163Integration {
244244
container.innerHTML = `
245245
<div class="buff163-header">
246246
<img src="https://buff.163.com/favicon.ico" alt="Buff163" class="buff163-icon">
247-
<span class="buff163-label">Buff163</span>
248-
${buffData.isMock ? '<span class="mock-badge" title="Mock data - connect to real API">DEMO</span>' : ''}
247+
<span class="buff163-label">Buff163 Estimate</span>
249248
</div>
250249
251250
<div class="buff163-price-row">

src/item3DPreview.js

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/**
2+
* 3D Item Preview - Displays item image on hover
3+
* Similar to CSFloat's item preview feature
4+
*/
5+
6+
class Item3DPreview {
7+
constructor() {
8+
this.previewElement = null;
9+
this.currentImageUrl = null;
10+
this.isVisible = false;
11+
this.init();
12+
}
13+
14+
init() {
15+
console.log('[CS2 Float] 3D Item Preview initialized');
16+
this.createPreviewElement();
17+
this.injectCSS();
18+
}
19+
20+
/**
21+
* Create the preview element
22+
*/
23+
createPreviewElement() {
24+
this.previewElement = document.createElement('div');
25+
this.previewElement.className = 'cs2-item-preview-tooltip';
26+
this.previewElement.style.display = 'none';
27+
28+
this.previewElement.innerHTML = `
29+
<div class="preview-container">
30+
<img class="preview-image" src="" alt="Item Preview">
31+
<div class="preview-loader">
32+
<div class="spinner"></div>
33+
<span>Loading...</span>
34+
</div>
35+
</div>
36+
`;
37+
38+
document.body.appendChild(this.previewElement);
39+
}
40+
41+
/**
42+
* Show preview for an item
43+
* @param {string} imageUrl - CDN URL for the item image
44+
* @param {HTMLElement} triggerElement - Element that triggered the preview
45+
*/
46+
showPreview(imageUrl, triggerElement) {
47+
if (!imageUrl || !this.previewElement) return;
48+
49+
const img = this.previewElement.querySelector('.preview-image');
50+
const loader = this.previewElement.querySelector('.preview-loader');
51+
52+
// Show loader
53+
loader.style.display = 'flex';
54+
img.style.display = 'none';
55+
56+
// If same image, just reposition
57+
if (this.currentImageUrl === imageUrl && img.complete) {
58+
img.style.display = 'block';
59+
loader.style.display = 'none';
60+
} else {
61+
// Load new image
62+
this.currentImageUrl = imageUrl;
63+
img.src = imageUrl;
64+
65+
img.onload = () => {
66+
img.style.display = 'block';
67+
loader.style.display = 'none';
68+
};
69+
70+
img.onerror = () => {
71+
loader.innerHTML = '<span style="color: #ef4444;">Failed to load image</span>';
72+
};
73+
}
74+
75+
// Position and show tooltip
76+
this.positionPreview(triggerElement);
77+
this.previewElement.style.display = 'block';
78+
this.isVisible = true;
79+
}
80+
81+
/**
82+
* Hide preview
83+
*/
84+
hidePreview() {
85+
if (this.previewElement) {
86+
this.previewElement.style.display = 'none';
87+
this.isVisible = false;
88+
}
89+
}
90+
91+
/**
92+
* Position preview relative to trigger element
93+
*/
94+
positionPreview(triggerElement) {
95+
if (!this.previewElement || !triggerElement) return;
96+
97+
const rect = triggerElement.getBoundingClientRect();
98+
const previewWidth = 400;
99+
const previewHeight = 300;
100+
101+
// Calculate position (prefer right side, fallback to left)
102+
let left = rect.right + 10;
103+
let top = rect.top + (rect.height / 2) - (previewHeight / 2);
104+
105+
// Check if it would go off screen
106+
if (left + previewWidth > window.innerWidth) {
107+
left = rect.left - previewWidth - 10;
108+
}
109+
110+
// Adjust vertical position if needed
111+
if (top < 10) {
112+
top = 10;
113+
} else if (top + previewHeight > window.innerHeight) {
114+
top = window.innerHeight - previewHeight - 10;
115+
}
116+
117+
this.previewElement.style.left = `${left}px`;
118+
this.previewElement.style.top = `${top}px`;
119+
}
120+
121+
/**
122+
* Attach preview to market listing items
123+
* @param {HTMLElement} listingElement - Market listing row
124+
* @param {string} imageUrl - Item image URL from API
125+
*/
126+
attachToListing(listingElement, imageUrl) {
127+
if (!listingElement || !imageUrl) return;
128+
129+
// Find the image container
130+
const imgContainer = listingElement.querySelector('.market_listing_item_img_container, .market_listing_item_img');
131+
if (!imgContainer) return;
132+
133+
// Add hover listeners
134+
imgContainer.addEventListener('mouseenter', () => {
135+
this.showPreview(imageUrl, imgContainer);
136+
});
137+
138+
imgContainer.addEventListener('mouseleave', () => {
139+
// Small delay to allow moving to tooltip
140+
setTimeout(() => {
141+
if (!this.previewElement.matches(':hover')) {
142+
this.hidePreview();
143+
}
144+
}, 100);
145+
});
146+
147+
// Keep visible when hovering over preview itself
148+
this.previewElement.addEventListener('mouseenter', () => {
149+
this.isVisible = true;
150+
});
151+
152+
this.previewElement.addEventListener('mouseleave', () => {
153+
this.hidePreview();
154+
});
155+
156+
// Add visual indicator
157+
imgContainer.style.cursor = 'pointer';
158+
imgContainer.title = 'Hover to preview 3D model';
159+
}
160+
161+
/**
162+
* Inject CSS styles
163+
*/
164+
injectCSS() {
165+
if (document.getElementById('cs2-3d-preview-styles')) {
166+
return;
167+
}
168+
169+
const style = document.createElement('style');
170+
style.id = 'cs2-3d-preview-styles';
171+
style.textContent = `
172+
.cs2-item-preview-tooltip {
173+
position: fixed;
174+
z-index: 10000;
175+
width: 400px;
176+
height: 300px;
177+
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
178+
border: 2px solid #3a3a3a;
179+
border-radius: 12px;
180+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
181+
pointer-events: auto;
182+
transition: opacity 0.2s ease-in-out;
183+
overflow: hidden;
184+
}
185+
186+
.preview-container {
187+
width: 100%;
188+
height: 100%;
189+
display: flex;
190+
align-items: center;
191+
justify-content: center;
192+
position: relative;
193+
}
194+
195+
.preview-image {
196+
max-width: 100%;
197+
max-height: 100%;
198+
object-fit: contain;
199+
display: none;
200+
}
201+
202+
.preview-loader {
203+
display: flex;
204+
flex-direction: column;
205+
align-items: center;
206+
gap: 15px;
207+
color: #a1a1aa;
208+
font-size: 14px;
209+
}
210+
211+
.spinner {
212+
width: 40px;
213+
height: 40px;
214+
border: 4px solid rgba(255, 255, 255, 0.1);
215+
border-top-color: #60a5fa;
216+
border-radius: 50%;
217+
animation: cs2-spin 0.8s linear infinite;
218+
}
219+
220+
@keyframes cs2-spin {
221+
to { transform: rotate(360deg); }
222+
}
223+
224+
/* Hover effect on market listing images */
225+
.market_listing_item_img_container:hover,
226+
.market_listing_item_img:hover {
227+
transform: scale(1.05);
228+
transition: transform 0.2s ease;
229+
}
230+
231+
.market_listing_item_img_container,
232+
.market_listing_item_img {
233+
transition: transform 0.2s ease;
234+
}
235+
`;
236+
237+
document.head.appendChild(style);
238+
}
239+
}
240+
241+
// Initialize on page load
242+
if (document.readyState === 'loading') {
243+
document.addEventListener('DOMContentLoaded', () => {
244+
window.item3DPreview = new Item3DPreview();
245+
});
246+
} else {
247+
window.item3DPreview = new Item3DPreview();
248+
}
249+
250+
// Make available globally
251+
if (typeof window !== 'undefined') {
252+
window.Item3DPreview = Item3DPreview;
253+
}

0 commit comments

Comments
 (0)