You value the item at a private amount. Three rival bidders will submit sealed bids too. You choose how much to bid, then the rules decide who wins and what they pay.
<p class="hint">You value the item at <b id="valLabel">$70</b>. Three rivals bid in secret. Pick the <b>auction rule</b>, choose <b>your bid</b>, and run it. Watch when bidding your true value is the smartest move.</p>
<div class="row">
<label>Your true value
<input id="value" type="range" min="10" max="100" step="5" value="70">
</label>
<label>Your bid
<input id="bid" type="range" min="0" max="100" step="5" value="70">
<span id="bidLabel">$70</span>
</label>
</div>
<div class="rules">
<button id="first" type="button" class="sel">First-price (pay your bid)</button>
<button id="second" type="button">Second-price (Vickrey)</button>
</div>
<div class="btns">
<button id="run" type="button">Run auction</button>
<button id="best" type="button" class="ghost">What if I bid my true value?</button>
</div>
<div id="bids" class="bids"></div>
<div class="status" id="status">Set your bid and run the auction.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.row { display: flex; gap: 1.2rem; flex-wrap: wrap; margin-bottom: .7rem; }
label { font-size: .85rem; font-weight: 600; color: #1d3557; display: flex; flex-direction: column; gap: .25rem; }
input[type=range] { width: 180px; }
.rules { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
.rules button { background: #fff; color: #1d3557; border: 1px solid #1d3557; }
.rules button.sel { background: #1d3557; color: #fff; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.bids { display: grid; gap: 4px; margin: .5rem 0; }
.brow { display: flex; align-items: center; gap: .6rem; font: 600 14px ui-monospace, monospace; }
.brow .name { width: 70px; }
.brow .bar { height: 18px; border-radius: 4px; background: #c9ccd1; }
.brow.you .bar { background: #457b9d; }
.brow.win .bar { background: #0a7d33; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; line-height: 1.4; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* A sealed-bid auction with one item. You have a private value V. Three rivals bid fixed amounts. Two rules:
first-price : winner pays own bid -> profit = V - yourBid (if you win)
second-price: winner pays 2nd-highest -> profit = V - secondHighestBid
Vickrey (1961) proved the second-price rule is truthful: bidding V is a
dominant strategy. This demo lets you see it directly. */
const RIVALS = [{name:'Ada',bid:55},{name:'Boris',bid:80},{name:'Cleo',bid:40}];
let rule = 'first';
const $ = id => document.getElementById(id);
function sync(){ $('valLabel').textContent='
Try the first-price rule first (the winner pays their own bid). Bid your true value and your profit is zero; the only way to make money is to shade your bid down and guess what the others will do — pure speculation. Now switch to the second-price (Vickrey) rule: the winner pays the second-highest bid. Replay it and notice the key fact — whatever the others do, you can never do better than by bidding exactly your true value. Honesty becomes the dominant strategy.
+$('value').value; $('bidLabel').textContent='
Try the first-price rule first (the winner pays their own bid). Bid your true value and your profit is zero; the only way to make money is to shade your bid down and guess what the others will do — pure speculation. Now switch to the second-price (Vickrey) rule: the winner pays the second-highest bid. Replay it and notice the key fact — whatever the others do, you can never do better than by bidding exactly your true value. Honesty becomes the dominant strategy.
+$('bid').value; }
// Returns {winner, price, yourProfit} for a given your-bid under the current rule.
function auction(yourBid){
const value = +$('value').value;
const all = [...RIVALS.map(r=>({name:r.name,bid:r.bid,you:false})), {name:`You`,bid:yourBid,you:true}];
// Highest bid wins; ties go to a rival (you only win by strictly outbidding).
let win = all[0];
for(const b of all){ if(b.bid > win.bid) win = b; }
const sorted = all.map(b=>b.bid).sort((a,b)=>b-a);
const second = sorted[1];
const price = rule==='first' ? win.bid : second;
const youWin = win.you;
const yourProfit = youWin ? value - price : 0;
return {all, win, price, youWin, yourProfit, value};
}
function renderBids(res){
const max = Math.max(100, ...res.all.map(b=>b.bid));
const wrap = $('bids'); wrap.innerHTML='';
res.all.slice().sort((a,b)=>b.bid-a.bid).forEach(b=>{
const row=document.createElement('div');
row.className='brow'+(b.you?' you':'')+(b===res.win?' win':'');
row.innerHTML='<span class="name">'+b.name+'</span>'+
'<span class="bar" style="width:'+(Math.max(b.bid,2)/max*180)+'px"></span>'+
'<span>
Try the first-price rule first (the winner pays their own bid). Bid your true value and your profit is zero; the only way to make money is to shade your bid down and guess what the others will do — pure speculation. Now switch to the second-price (Vickrey) rule: the winner pays the second-highest bid. Replay it and notice the key fact — whatever the others do, you can never do better than by bidding exactly your true value. Honesty becomes the dominant strategy.
+b.bid+(b===res.win?' ◄ wins':'')+'</span>';
wrap.appendChild(row);
});
}
function setStatus(t,cls){ const s=$('status'); s.innerHTML=t; s.className='status '+cls; }
function run(yourBid){
const res = auction(yourBid);
renderBids(res);
const ruleName = rule==='first' ? `First-price` : `Second-price (Vickrey)`;
if(res.youWin){
const sign = res.yourProfit>=0 ? `profit` : `LOSS`;
setStatus(fmt(`%ruleName%: you win, pay $%price%. Your value is $%value% → %sign% of $%abs%.`, { ruleName: ruleName, price: res.price, value: res.value, sign: sign, abs: Math.abs(res.yourProfit) }), res.yourProfit>=0?'ok':'bad');
} else {
setStatus(fmt(`%ruleName%: %name% wins at $%price%. You get $0 (no item).`, { ruleName: ruleName, name: res.win.name, price: res.price }),'bad');
}
console.log(fmt(`rule=%rule%`, { rule: rule }),fmt(`yourBid=%yourBid%`, { yourBid: yourBid }),fmt(`price=$%price%`, { price: res.price }),fmt(`profit=$%yourProfit%`, { yourProfit: res.yourProfit }));
}
$('first').addEventListener('click',()=>{ rule='first'; $('first').classList.add('sel'); $('second').classList.remove('sel'); });
$('second').addEventListener('click',()=>{ rule='second'; $('second').classList.add('sel'); $('first').classList.remove('sel'); });
$('value').addEventListener('input',sync);
$('bid').addEventListener('input',sync);
$('run').addEventListener('click',()=>run(+$('bid').value));
$('best').addEventListener('click',()=>{ $('bid').value=$('value').value; sync(); run(+$('value').value); });
sync(); run(70);
<p class="hint">You value the item at <b id="valLabel">$70</b>. Three rivals bid in secret. Pick the <b>auction rule</b>, choose <b>your bid</b>, and run it. Watch when bidding your true value is the smartest move.</p>
<div class="row">
<label>Your true value
<input id="value" type="range" min="10" max="100" step="5" value="70">
</label>
<label>Your bid
<input id="bid" type="range" min="0" max="100" step="5" value="70">
<span id="bidLabel">$70</span>
</label>
</div>
<div class="rules">
<button id="first" type="button" class="sel">First-price (pay your bid)</button>
<button id="second" type="button">Second-price (Vickrey)</button>
</div>
<div class="btns">
<button id="run" type="button">Run auction</button>
<button id="best" type="button" class="ghost">What if I bid my true value?</button>
</div>
<div id="bids" class="bids"></div>
<div class="status" id="status">Set your bid and run the auction.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.row { display: flex; gap: 1.2rem; flex-wrap: wrap; margin-bottom: .7rem; }
label { font-size: .85rem; font-weight: 600; color: #1d3557; display: flex; flex-direction: column; gap: .25rem; }
input[type=range] { width: 180px; }
.rules { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
.rules button { background: #fff; color: #1d3557; border: 1px solid #1d3557; }
.rules button.sel { background: #1d3557; color: #fff; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .6rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.bids { display: grid; gap: 4px; margin: .5rem 0; }
.brow { display: flex; align-items: center; gap: .6rem; font: 600 14px ui-monospace, monospace; }
.brow .name { width: 70px; }
.brow .bar { height: 18px; border-radius: 4px; background: #c9ccd1; }
.brow.you .bar { background: #457b9d; }
.brow.win .bar { background: #0a7d33; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; line-height: 1.4; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
/* A sealed-bid auction with one item. You have a private value V. Three rivals bid fixed amounts. Two rules:
first-price : winner pays own bid -> profit = V - yourBid (if you win)
second-price: winner pays 2nd-highest -> profit = V - secondHighestBid
Vickrey (1961) proved the second-price rule is truthful: bidding V is a
dominant strategy. This demo lets you see it directly. */
const RIVALS = [{name:'Ada',bid:55},{name:'Boris',bid:80},{name:'Cleo',bid:40}];
let rule = 'first';
const $ = id => document.getElementById(id);
function sync(){ $('valLabel').textContent='
Try the first-price rule first (the winner pays their own bid). Bid your true value and your profit is zero; the only way to make money is to shade your bid down and guess what the others will do — pure speculation. Now switch to the second-price (Vickrey) rule: the winner pays the second-highest bid. Replay it and notice the key fact — whatever the others do, you can never do better than by bidding exactly your true value. Honesty becomes the dominant strategy.
+$('value').value; $('bidLabel').textContent='
Try the first-price rule first (the winner pays their own bid). Bid your true value and your profit is zero; the only way to make money is to shade your bid down and guess what the others will do — pure speculation. Now switch to the second-price (Vickrey) rule: the winner pays the second-highest bid. Replay it and notice the key fact — whatever the others do, you can never do better than by bidding exactly your true value. Honesty becomes the dominant strategy.
+$('bid').value; }
// Returns {winner, price, yourProfit} for a given your-bid under the current rule.
function auction(yourBid){
const value = +$('value').value;
const all = [...RIVALS.map(r=>({name:r.name,bid:r.bid,you:false})), {name:`You`,bid:yourBid,you:true}];
// Highest bid wins; ties go to a rival (you only win by strictly outbidding).
let win = all[0];
for(const b of all){ if(b.bid > win.bid) win = b; }
const sorted = all.map(b=>b.bid).sort((a,b)=>b-a);
const second = sorted[1];
const price = rule==='first' ? win.bid : second;
const youWin = win.you;
const yourProfit = youWin ? value - price : 0;
return {all, win, price, youWin, yourProfit, value};
}
function renderBids(res){
const max = Math.max(100, ...res.all.map(b=>b.bid));
const wrap = $('bids'); wrap.innerHTML='';
res.all.slice().sort((a,b)=>b.bid-a.bid).forEach(b=>{
const row=document.createElement('div');
row.className='brow'+(b.you?' you':'')+(b===res.win?' win':'');
row.innerHTML='<span class="name">'+b.name+'</span>'+
'<span class="bar" style="width:'+(Math.max(b.bid,2)/max*180)+'px"></span>'+
'<span>
Try the first-price rule first (the winner pays their own bid). Bid your true value and your profit is zero; the only way to make money is to shade your bid down and guess what the others will do — pure speculation. Now switch to the second-price (Vickrey) rule: the winner pays the second-highest bid. Replay it and notice the key fact — whatever the others do, you can never do better than by bidding exactly your true value. Honesty becomes the dominant strategy.
+b.bid+(b===res.win?' ◄ wins':'')+'</span>';
wrap.appendChild(row);
});
}
function setStatus(t,cls){ const s=$('status'); s.innerHTML=t; s.className='status '+cls; }
function run(yourBid){
const res = auction(yourBid);
renderBids(res);
const ruleName = rule==='first' ? `First-price` : `Second-price (Vickrey)`;
if(res.youWin){
const sign = res.yourProfit>=0 ? `profit` : `LOSS`;
setStatus(fmt(`%ruleName%: you win, pay $%price%. Your value is $%value% → %sign% of $%abs%.`, { ruleName: ruleName, price: res.price, value: res.value, sign: sign, abs: Math.abs(res.yourProfit) }), res.yourProfit>=0?'ok':'bad');
} else {
setStatus(fmt(`%ruleName%: %name% wins at $%price%. You get $0 (no item).`, { ruleName: ruleName, name: res.win.name, price: res.price }),'bad');
}
console.log(fmt(`rule=%rule%`, { rule: rule }),fmt(`yourBid=%yourBid%`, { yourBid: yourBid }),fmt(`price=$%price%`, { price: res.price }),fmt(`profit=$%yourProfit%`, { yourProfit: res.yourProfit }));
}
$('first').addEventListener('click',()=>{ rule='first'; $('first').classList.add('sel'); $('second').classList.remove('sel'); });
$('second').addEventListener('click',()=>{ rule='second'; $('second').classList.add('sel'); $('first').classList.remove('sel'); });
$('value').addEventListener('input',sync);
$('bid').addEventListener('input',sync);
$('run').addEventListener('click',()=>run(+$('bid').value));
$('best').addEventListener('click',()=>{ $('bid').value=$('value').value; sync(); run(+$('value').value); });
sync(); run(70);
Comments
Loading comments...