Initial commit
This commit is contained in:
425
js/demos/bounty.js
Normal file
425
js/demos/bounty.js
Normal file
@@ -0,0 +1,425 @@
|
||||
window.GDD = window.GDD || {};
|
||||
|
||||
const { useState, useEffect, useCallback, useRef } = React;
|
||||
|
||||
function BountyDemo() {
|
||||
const [bounties, setBounties] = useState([]);
|
||||
const [killFeed, setKillFeed] = useState([]);
|
||||
const [placeBountyTarget, setPlaceBountyTarget] = useState('');
|
||||
const [placeBountyAmount, setPlaceBountyAmount] = useState(5000);
|
||||
const [showPlaceBounty, setShowPlaceBounty] = useState(false);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [autoFeed, setAutoFeed] = useState(false);
|
||||
const feedRef = useRef(null);
|
||||
const autoRef = useRef(null);
|
||||
|
||||
const feedNames = [
|
||||
'CMDR Picard', 'CMDR Worf', 'CMDR Data', 'CMDR Troi', 'CMDR Riker',
|
||||
'MinerBob', 'PirateKing99', 'NullSecWarlord', 'TraderAlice', 'DeepMiner',
|
||||
'RockHound', 'AmarrTrader', 'BulkMiner', 'GallenteForge', 'HighSecOps',
|
||||
];
|
||||
|
||||
const shipTypes = ['Frigate', 'Destroyer', 'Cruiser', 'Battlecruiser', 'Battleship', 'Hauler', 'Mining Barge'];
|
||||
|
||||
const systems = ['Sol', 'Amarr', 'Hek', 'Rens', 'Dodixie', 'U-IRTYR', 'PF-346', 'YZ-LQL', 'O-WAMW'];
|
||||
|
||||
const tierConfig = [
|
||||
{ tier: 'Petty', threshold: 500, color: 'var(--muted)', reward: '10%', visibility: 'System-local' },
|
||||
{ tier: 'Standard', threshold: 5000, color: 'var(--cyan)', reward: '15%', visibility: 'Regional' },
|
||||
{ tier: 'Dangerous', threshold: 50000, color: 'var(--accent)', reward: '20%', visibility: 'Galaxy-wide' },
|
||||
{ tier: 'Most Wanted', threshold: 500000, color: 'var(--red)', reward: '25%', visibility: 'Galaxy + Leaderboard' },
|
||||
];
|
||||
|
||||
const getTier = (pool) => {
|
||||
if (pool >= 500000) return tierConfig[3];
|
||||
if (pool >= 50000) return tierConfig[2];
|
||||
if (pool >= 5000) return tierConfig[1];
|
||||
return tierConfig[0];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.GDD.api.getBounties().then(b => setBounties(b));
|
||||
window.GDD.api.getKillFeed().then(k => setKillFeed(k));
|
||||
}, []);
|
||||
|
||||
const addNotif = useCallback((msg, color) => {
|
||||
const id = Date.now();
|
||||
setNotifications(prev => [...prev, { id, msg, color }]);
|
||||
setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3500);
|
||||
}, []);
|
||||
|
||||
const handlePlaceBounty = useCallback(async () => {
|
||||
if (!placeBountyTarget.trim()) return;
|
||||
if (placeBountyAmount < 500) {
|
||||
addNotif('Minimum bounty is 500 ISK.', 'var(--red)');
|
||||
return;
|
||||
}
|
||||
const result = await window.GDD.api.placeBounty(placeBountyTarget, placeBountyAmount);
|
||||
if (result.success) {
|
||||
setBounties(prev => {
|
||||
const existing = prev.find(b => b.target === placeBountyTarget);
|
||||
if (existing) {
|
||||
return prev.map(b => b.target === placeBountyTarget
|
||||
? { ...b, pool: b.pool + placeBountyAmount, tier: getTier(b.pool + placeBountyAmount).tier }
|
||||
: b);
|
||||
}
|
||||
return [...prev, {
|
||||
target: placeBountyTarget,
|
||||
pool: placeBountyAmount,
|
||||
tier: getTier(placeBountyAmount).tier,
|
||||
lastHostile: 'Just now',
|
||||
}];
|
||||
});
|
||||
addNotif(`Bounty of ₢${placeBountyAmount.toLocaleString()} placed on ${placeBountyTarget}.`, 'var(--green)');
|
||||
setShowPlaceBounty(false);
|
||||
setPlaceBountyTarget('');
|
||||
setPlaceBountyAmount(5000);
|
||||
}
|
||||
}, [placeBountyTarget, placeBountyAmount, addNotif]);
|
||||
|
||||
// Auto-generate kill feed
|
||||
const generateKill = useCallback(() => {
|
||||
const victim = feedNames[Math.floor(Math.random() * feedNames.length)];
|
||||
let killer;
|
||||
do { killer = feedNames[Math.floor(Math.random() * feedNames.length)]; } while (killer === victim);
|
||||
const ship = shipTypes[Math.floor(Math.random() * shipTypes.length)];
|
||||
const system = systems[Math.floor(Math.random() * systems.length)];
|
||||
const bounty = Math.random() > 0.6 ? Math.floor(Math.random() * 50000) : 0;
|
||||
|
||||
const kill = {
|
||||
victim,
|
||||
killer,
|
||||
ship,
|
||||
system,
|
||||
bounty,
|
||||
time: 'Just now',
|
||||
};
|
||||
|
||||
setKillFeed(prev => [kill, ...prev.slice(0, 49)]);
|
||||
|
||||
// If bounty, update bounty pool
|
||||
if (bounty > 0) {
|
||||
addNotif(`Bounty collected: ${killer} claimed ₢${bounty.toLocaleString()} from ${victim}'s bounty.`, 'var(--accent)');
|
||||
setBounties(prev => prev.map(b =>
|
||||
b.target === victim
|
||||
? { ...b, pool: Math.max(0, b.pool - bounty) }
|
||||
: b
|
||||
).filter(b => b.pool > 0));
|
||||
}
|
||||
}, [addNotif]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFeed) {
|
||||
autoRef.current = setInterval(generateKill, 2000 + Math.random() * 3000);
|
||||
} else {
|
||||
if (autoRef.current) clearInterval(autoRef.current);
|
||||
}
|
||||
return () => { if (autoRef.current) clearInterval(autoRef.current); };
|
||||
}, [autoFeed, generateKill]);
|
||||
|
||||
// Auto-scroll kill feed
|
||||
useEffect(() => {
|
||||
if (killFeed.length > 0 && feedRef.current) {
|
||||
// No auto-scroll needed since newest are on top
|
||||
}
|
||||
}, [killFeed]);
|
||||
|
||||
const totalBountyPool = bounties.reduce((sum, b) => sum + b.pool, 0);
|
||||
const totalKills = killFeed.length;
|
||||
const totalBountyCollected = killFeed.reduce((sum, k) => sum + k.bounty, 0);
|
||||
|
||||
return (
|
||||
<div className="content-inner">
|
||||
<a href="#overview" style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '0.75rem', fontFamily: 'var(--font-mono)', color: 'var(--muted)', textDecoration: 'none', marginBottom: 'var(--sp-3)', transition: 'color 0.15s' }} onMouseEnter={e => e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs</a>
|
||||
<h1 style={{ marginBottom: '8px' }}>Bounty Board & Kill Feed</h1>
|
||||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem' }}>
|
||||
Live bounty board with tier escalation and a galaxy-wide kill feed. Place bounties on pirates,
|
||||
track kill events, and watch bounty pools climb. Toggle the auto-feed to simulate live combat activity.
|
||||
</p>
|
||||
|
||||
{/* HUD-style bounty strip */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 'var(--sp-4)',
|
||||
padding: 'var(--sp-3) var(--sp-4)', marginTop: 'var(--sp-4)', marginBottom: 'var(--sp-3)',
|
||||
background: 'rgba(15,22,35,0.88)', border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-lg)', backdropFilter: 'blur(8px)',
|
||||
fontFamily: 'var(--font-mono)', fontSize: '0.75rem',
|
||||
}}>
|
||||
<span style={{ color: 'var(--fg-bright)', fontWeight: 600, fontSize: '0.8rem' }}>BOUNTY BOARD</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>ACTIVE</span>
|
||||
<span style={{ color: 'var(--red)', fontWeight: 600 }}>{bounties.length}</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>POOL</span>
|
||||
<span style={{ color: 'var(--accent)', fontWeight: 600 }}>₢{totalBountyPool.toLocaleString()}</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>COLLECTED</span>
|
||||
<span style={{ color: 'var(--green)', fontWeight: 600 }}>₢{totalBountyCollected.toLocaleString()}</span>
|
||||
{autoFeed && <span style={{ marginLeft: 'auto', color: 'var(--red)', fontSize: '0.7rem' }}>● LIVE FEED</span>}
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<div style={{ position: 'fixed', top: 'var(--sp-4)', right: 'var(--sp-4)', zIndex: 1000, display: 'flex', flexDirection: 'column', gap: 'var(--sp-2)' }}>
|
||||
{notifications.map(n => (
|
||||
<div key={n.id} style={{
|
||||
background: 'var(--surface)', border: `1px solid ${n.color}40`,
|
||||
borderRadius: 'var(--radius-md)', padding: 'var(--sp-3) var(--sp-4)',
|
||||
fontSize: '0.8rem', color: n.color, boxShadow: 'var(--shadow-md)',
|
||||
maxWidth: '400px',
|
||||
}}>
|
||||
{n.msg}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="stat-grid">
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--red)' }}>{bounties.length}</div>
|
||||
<div className="stat-label">Active Bounties</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--accent)' }}>₢{totalBountyPool.toLocaleString()}</div>
|
||||
<div className="stat-label">Total Bounty Pool</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--cyan)' }}>{totalKills}</div>
|
||||
<div className="stat-label">Kill Events</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--green)' }}>₢{totalBountyCollected.toLocaleString()}</div>
|
||||
<div className="stat-label">Bounty Collected</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 'var(--sp-3)', marginBottom: 'var(--sp-5)', flexWrap: 'wrap' }}>
|
||||
<button className={`btn ${autoFeed ? 'btn-danger' : 'btn-primary'}`} onClick={() => setAutoFeed(!autoFeed)}>
|
||||
{autoFeed ? '■ Stop Live Feed' : '▶ Start Live Feed'}
|
||||
</button>
|
||||
<button className="btn" onClick={() => setShowPlaceBounty(!showPlaceBounty)}>
|
||||
+ Place Bounty
|
||||
</button>
|
||||
<button className="btn" onClick={generateKill}>
|
||||
Generate Kill Event
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Place bounty modal */}
|
||||
{showPlaceBounty && (
|
||||
<div style={{
|
||||
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 2000,
|
||||
}} onClick={() => setShowPlaceBounty(false)}>
|
||||
<div style={{
|
||||
background: 'var(--surface)', border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-lg)', padding: 'var(--sp-6)', minWidth: 360,
|
||||
}} onClick={e => e.stopPropagation()}>
|
||||
<h3 style={{ marginBottom: 'var(--sp-4)', color: 'var(--red)' }}>Place a Bounty</h3>
|
||||
|
||||
<div style={{ marginBottom: 'var(--sp-3)' }}>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--muted)', marginBottom: 'var(--sp-1)' }}>Target Player</div>
|
||||
<input type="text" value={placeBountyTarget}
|
||||
onChange={e => setPlaceBountyTarget(e.target.value)}
|
||||
placeholder="Enter player name..."
|
||||
style={{
|
||||
width: '100%', padding: 'var(--sp-2) var(--sp-3)', background: 'var(--surface-raised)',
|
||||
border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
|
||||
color: 'var(--fg)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 'var(--sp-3)' }}>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--muted)', marginBottom: 'var(--sp-1)' }}>
|
||||
Amount (min 500 ISK)
|
||||
</div>
|
||||
<input type="number" value={placeBountyAmount} min={500}
|
||||
onChange={e => setPlaceBountyAmount(parseInt(e.target.value) || 0)}
|
||||
style={{
|
||||
width: '100%', padding: 'var(--sp-2) var(--sp-3)', background: 'var(--surface-raised)',
|
||||
border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
|
||||
color: 'var(--fg)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tier preview */}
|
||||
<div style={{ marginBottom: 'var(--sp-4)', padding: 'var(--sp-3)', background: 'var(--surface-raised)', borderRadius: 'var(--radius-md)' }}>
|
||||
<div style={{ fontSize: '0.7rem', color: 'var(--muted)', marginBottom: 'var(--sp-1)' }}>Resulting Tier</div>
|
||||
{(() => {
|
||||
const t = getTier(placeBountyAmount);
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.85rem', color: t.color, fontWeight: 600 }}>
|
||||
{t.tier}
|
||||
</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)' }}>
|
||||
Hunter reward: {t.reward} · {t.visibility}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', gap: 'var(--sp-3)' }}>
|
||||
<button className="btn btn-primary" style={{ flex: 1, background: 'var(--red)', borderColor: 'var(--red)' }}
|
||||
onClick={handlePlaceBounty}>
|
||||
Place Bounty
|
||||
</button>
|
||||
<button className="btn" onClick={() => setShowPlaceBounty(false)}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid-2">
|
||||
{/* Active Bounties */}
|
||||
<div>
|
||||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>
|
||||
<span style={{ color: 'var(--red)' }}>◉</span> Active Bounties
|
||||
</h3>
|
||||
|
||||
{bounties.length === 0 && (
|
||||
<div className="card" style={{ textAlign: 'center', color: 'var(--muted)', padding: 'var(--sp-8)' }}>
|
||||
No active bounties. Place one to get started.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{bounties.sort((a, b) => b.pool - a.pool).map((bounty, i) => {
|
||||
const tier = getTier(bounty.pool);
|
||||
return (
|
||||
<div key={i} className="card" style={{
|
||||
marginBottom: 'var(--sp-3)',
|
||||
borderLeft: `3px solid ${tier.color}`,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--sp-3)' }}>
|
||||
<div>
|
||||
<h4 style={{ margin: 0, color: 'var(--fg-bright)' }}>{bounty.target}</h4>
|
||||
<span className="pill" style={{
|
||||
background: tier.color + '15', color: tier.color,
|
||||
border: `1px solid ${tier.color}40`, fontSize: '0.6rem',
|
||||
}}>
|
||||
{tier.tier.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '1.1rem', color: 'var(--accent)', fontWeight: 700 }}>
|
||||
₢{bounty.pool.toLocaleString()}
|
||||
</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.65rem', color: 'var(--muted)' }}>
|
||||
Hunter reward: {tier.reward}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.75rem' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>Visibility: <span style={{ color: tier.color }}>{tier.visibility}</span></span>
|
||||
<span style={{ color: 'var(--muted)' }}>Last hostile: {bounty.lastHostile}</span>
|
||||
</div>
|
||||
|
||||
{/* Pool bar */}
|
||||
<div style={{ marginTop: 'var(--sp-3)' }}>
|
||||
<div className="progress-bar" style={{ height: '4px' }}>
|
||||
<div className="fill" style={{
|
||||
width: `${Math.min(100, (bounty.pool / 500000) * 100)}%`,
|
||||
background: tier.color,
|
||||
}} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontFamily: 'var(--font-mono)', fontSize: '0.6rem', color: 'var(--muted)', marginTop: '2px' }}>
|
||||
<span>₢{tier.threshold.toLocaleString()}</span>
|
||||
<span>Next tier</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Tier legend */}
|
||||
<div className="card" style={{ marginTop: 'var(--sp-4)' }}>
|
||||
<h4 style={{ marginBottom: 'var(--sp-3)' }}>Bounty Tiers</h4>
|
||||
{tierConfig.map((t, i) => (
|
||||
<div key={i} style={{
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
padding: 'var(--sp-2) 0', borderBottom: '1px solid var(--border)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-2)' }}>
|
||||
<span style={{ color: t.color, fontWeight: 600, fontSize: '0.85rem' }}>{t.tier}</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--muted)' }}>
|
||||
≥ ₢{t.threshold.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>
|
||||
{t.reward} · {t.visibility}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Kill Feed */}
|
||||
<div>
|
||||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>
|
||||
<span style={{ color: 'var(--accent)' }}>✸</span> Kill Feed
|
||||
{autoFeed && (
|
||||
<span style={{ marginLeft: 'var(--sp-2)', fontFamily: 'var(--font-mono)', fontSize: '0.65rem', color: 'var(--red)' }}>
|
||||
● LIVE
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
|
||||
<div ref={feedRef} style={{ maxHeight: '600px', overflowY: 'auto' }}>
|
||||
{killFeed.length === 0 && (
|
||||
<div className="card" style={{ textAlign: 'center', color: 'var(--muted)', padding: 'var(--sp-8)' }}>
|
||||
No kill events yet. Start the live feed or generate events manually.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{killFeed.map((kill, i) => (
|
||||
<div key={i} style={{
|
||||
padding: 'var(--sp-3) var(--sp-4)',
|
||||
marginBottom: 'var(--sp-2)',
|
||||
background: i === 0 && autoFeed ? 'var(--accent-bg)' : 'var(--surface)',
|
||||
border: `1px solid ${kill.bounty > 0 ? 'var(--accent-border)' : 'var(--border)'}`,
|
||||
borderRadius: 'var(--radius-md)',
|
||||
transition: 'background 0.3s',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<span style={{ color: 'var(--red)', fontSize: '0.85rem' }}>{kill.victim}</span>
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.8rem', margin: '0 var(--sp-2)' }}>destroyed by</span>
|
||||
<span style={{ color: 'var(--cyan)', fontSize: '0.85rem' }}>{kill.killer}</span>
|
||||
</div>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.65rem', color: 'var(--muted)' }}>
|
||||
{kill.time}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--sp-4)', marginTop: 'var(--sp-1)', fontSize: '0.75rem' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>
|
||||
Ship: <span style={{ color: 'var(--fg-dim)' }}>{kill.ship}</span>
|
||||
</span>
|
||||
<span style={{ color: 'var(--muted)' }}>
|
||||
System: <span style={{ color: 'var(--fg-dim)' }}>{kill.system}</span>
|
||||
</span>
|
||||
{kill.bounty > 0 && (
|
||||
<span style={{ color: 'var(--accent)', fontFamily: 'var(--font-mono)' }}>
|
||||
Bounty: ₢{kill.bounty.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Anti-abuse rules */}
|
||||
<div className="callout callout-warn" style={{ marginTop: 'var(--sp-5)' }}>
|
||||
<strong>Anti-abuse rules (implemented in backend):</strong> You cannot claim your own bounty (alt check).
|
||||
Payout never exceeds ship loss value. Minimum placement is 500 ISK. Target must have negative security
|
||||
status or committed a hostile act within 24h. Bounties decay 10%/week if target stays clean for 30 days.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
window.GDD.BountyDemo = BountyDemo;
|
||||
Reference in New Issue
Block a user