diff --git a/src/css/styles.css b/src/css/styles.css
index 835cd4d..16835b2 100644
--- a/src/css/styles.css
+++ b/src/css/styles.css
@@ -246,15 +246,9 @@ tr.lot-child.la-sol:hover > td{background:rgba(153,69,255,.08)}
.hcard-hero{padding:13px 13px 9px}
.hcard-hero-lbl{font-size:.55rem;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--mu);margin-bottom:4px;font-family:var(--mono)}
.hcard-hero-val{font-size:1.3rem;font-weight:700;color:var(--orange);letter-spacing:-.5px;line-height:1;font-family:var(--mono)}
-.hcard-hero-sub{font-size:.6rem;color:var(--mu2);margin-top:5px;font-family:var(--mono)}
-.hcard-hero-sub span{color:var(--green);font-weight:600}
-.hcard-bar-wrap{padding:0 13px 12px}
-.hcard-bar-labels{display:flex;justify-content:space-between;font-size:.54rem;color:var(--mu);letter-spacing:.8px;text-transform:uppercase;margin-bottom:5px;font-family:var(--mono)}
-.hcard-bar-track{height:3px;background:var(--bd2);overflow:hidden}
-.hcard-bar-fill{height:100%;background:linear-gradient(90deg,var(--green),rgba(0,214,143,.5))}
-.hcard-stats{display:grid;grid-template-columns:1fr 1fr;border-top:1px solid var(--bd)}
+.hcard-stats{display:grid;grid-template-columns:1fr 1fr 1fr;border-top:1px solid var(--bd)}
.hcard-stat{padding:9px 13px}
-.hcard-stat:first-child{border-right:1px solid var(--bd)}
+.hcard-stat + .hcard-stat{border-left:1px solid var(--bd)}
.hcard-stat-lbl{font-size:.54rem;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;color:var(--mu);margin-bottom:3px;font-family:var(--mono)}
.hcard-stat-val{font-size:.8rem;font-weight:600;color:var(--text2);font-family:var(--mono)}
.hcard-stat-val.green{color:var(--green)}
diff --git a/src/js/06-render-table.js b/src/js/06-render-table.js
index 028ade0..a10725c 100644
--- a/src/js/06-render-table.js
+++ b/src/js/06-render-table.js
@@ -312,8 +312,7 @@ function rTable(displayRows, streams, lots) {
assetLots.forEach(lot => {
openLotCount++;
const nc = lot.netCost;
- const reduction = lot.costBasis - nc;
- const reductionPct = lot.costBasis > 0 ? (reduction / lot.costBasis * 100) : 0;
+ const reductionPct = lot.costBasis > 0 ? ((lot.costBasis - nc) / lot.costBasis * 100) : 0;
const lotBadge = totalAssetLots > 1 ? 'Lot ' + lot.lotNum + '' : '';
const holdingTrade = trades.find(t => t.id === lot.tradeIds[0]);
const isManualHolding = holdingTrade && holdingTrade.type === 'HOLDING';
@@ -362,16 +361,12 @@ function rTable(displayRows, streams, lots) {
+ '
'
+ '
Net Cost / ' + a + ' ⓘ
'
+ '
$' + fmt(nc) + '
'
- + '
basis $' + fmt(lot.costBasis) + ' — saved $' + fmt(reduction) + ' (' + reductionPct.toFixed(1) + '%)
'
+ '
'
+ spotBlock
- + ''
- + '
Premium reduction' + reductionPct.toFixed(1) + '%
'
- + '
'
- + '
'
+ ''
- + '
CC Premiums
$' + fmt(lot.lotPremiums) + '
'
+ '
Cost Basis
$' + fmt(lot.costBasis) + '
'
+ + '
CC Premiums
$' + fmt(lot.lotPremiums) + '
'
+ + '
Premium Reduction %
' + reductionPct.toFixed(1) + '%
'
+ '
'
+ '';
});
diff --git a/test/integration/holding-card-trim.test.js b/test/integration/holding-card-trim.test.js
new file mode 100644
index 0000000..b9f60f7
--- /dev/null
+++ b/test/integration/holding-card-trim.test.js
@@ -0,0 +1,70 @@
+const test = require('node:test');
+const assert = require('node:assert');
+const { setupJsdom } = require('../helpers/setupJsdom');
+
+// Issue #51: Trim redundant content from holding cards.
+// - Hero sub-line ("basis $X — saved $Y") removed.
+// - Premium-reduction bar removed.
+// - Footer stats: Cost Basis | CC Premiums | Premium Reduction % (3 cols, in order).
+
+function getHcard(window) {
+ const card = window.document.querySelector('.hcard');
+ assert.ok(card, 'expected a .hcard element on the page');
+ return card;
+}
+
+const baseTrades = [
+ { id: 1, asset: 'ETH', type: 'HOLDING', date: '2026-01-01', expiry: '',
+ dte: null, strike: 3000, size: 1, premium: 0, outcome: 'OPEN',
+ closeCost: 0, platform: 'SPOT' },
+ { id: 2, asset: 'ETH', type: 'CALL', date: '2026-01-15', expiry: '2026-01-29',
+ dte: 14, strike: 3500, size: 1, premium: 60, outcome: 'EXPIRED',
+ closeCost: 0, platform: 'RYSK' },
+];
+
+test('holding card has no hero sub-line', (t) => {
+ const { window, teardown } = setupJsdom({ trades: baseTrades });
+ t.after(teardown);
+ const card = getHcard(window);
+ assert.strictEqual(card.querySelector('.hcard-hero-sub'), null,
+ '.hcard-hero-sub should be removed');
+});
+
+test('holding card has no premium-reduction bar', (t) => {
+ const { window, teardown } = setupJsdom({ trades: baseTrades });
+ t.after(teardown);
+ const card = getHcard(window);
+ assert.strictEqual(card.querySelector('.hcard-bar-wrap'), null,
+ '.hcard-bar-wrap should be removed');
+});
+
+test('holding card footer has 3 stats: Cost Basis | CC Premiums | Premium Reduction %', (t) => {
+ const { window, teardown } = setupJsdom({ trades: baseTrades });
+ t.after(teardown);
+ const card = getHcard(window);
+
+ const stats = card.querySelectorAll('.hcard-stats .hcard-stat');
+ assert.strictEqual(stats.length, 3, 'footer should have exactly 3 stat cells');
+
+ const labels = Array.from(stats).map(s =>
+ s.querySelector('.hcard-stat-lbl').textContent.trim());
+ assert.deepStrictEqual(labels, ['Cost Basis', 'CC Premiums', 'Premium Reduction %'],
+ 'stat labels must appear in this order');
+
+ // Net cost = 3000 - 60 = 2940; reduction = 60/3000 = 2.0%.
+ const values = Array.from(stats).map(s =>
+ s.querySelector('.hcard-stat-val').textContent.trim());
+ assert.match(values[0], /\$3,?000(\.\d+)?$/, `Cost Basis value, got "${values[0]}"`);
+ assert.match(values[1], /\$60(\.\d+)?$/, `CC Premiums value, got "${values[1]}"`);
+ assert.match(values[2], /^2\.0%$/, `Reduction % value, got "${values[2]}"`);
+});
+
+test('Cost Basis appears exactly once on the card', (t) => {
+ const { window, teardown } = setupJsdom({ trades: baseTrades });
+ t.after(teardown);
+ const card = getHcard(window);
+ const labels = Array.from(card.querySelectorAll('.hcard-stat-lbl'))
+ .map(el => el.textContent.trim());
+ const cbCount = labels.filter(l => /^Cost Basis$/i.test(l)).length;
+ assert.strictEqual(cbCount, 1, 'Cost Basis should appear exactly once');
+});