Game — Editions / Bundle Compare Table
A dark neon editions comparison table for a fictional AAA release with Standard, Deluxe, and Ultimate columns: edition art headers, strike-through pricing with savings badges, a feature matrix using check, cross, and partial marks, and a glowing best-value Ultimate column. Vanilla JS powers a one-time versus 4-payment plan toggle, USD/EUR/GBP currency switching, hover column highlighting, collapsible what-is-included row groups, and toast feedback on every Buy CTA.
MCP
Code
:root {
--bg: #0a0b10;
--bg-2: #12131c;
--panel: #171926;
--panel-2: #1f2233;
--text: #e7e9f3;
--muted: #9aa0bf;
--line: rgba(231, 233, 243, 0.1);
--line-2: rgba(231, 233, 243, 0.18);
--accent: #00e5ff;
--accent-2: #7c4dff;
--accent-3: #ff3d71;
--success: #36e27a;
--warn: #ffc857;
--danger: #ff4d4d;
--glow: 0 0 18px rgba(0, 229, 255, 0.45);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Inter", system-ui, sans-serif;
background:
radial-gradient(900px 420px at 80% -10%, rgba(124, 77, 255, 0.16), transparent 60%),
radial-gradient(700px 380px at 8% 0%, rgba(0, 229, 255, 0.1), transparent 55%),
var(--bg);
color: var(--text);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.page {
max-width: 1100px;
margin: 0 auto;
padding: 48px 20px 72px;
}
/* ---------- Hero ---------- */
.hero {
text-align: center;
margin-bottom: 36px;
}
.hero-kicker {
font-family: "Orbitron", sans-serif;
font-weight: 500;
font-size: 0.72rem;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 10px;
}
.hero-title {
font-family: "Orbitron", sans-serif;
font-weight: 900;
font-size: clamp(2rem, 5.5vw, 3.4rem);
letter-spacing: 0.06em;
text-transform: uppercase;
margin: 0 0 12px;
}
.hero-title span {
background: linear-gradient(100deg, var(--accent), var(--accent-2));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 32px rgba(0, 229, 255, 0.35);
}
.hero-sub {
max-width: 56ch;
margin: 0 auto 26px;
color: var(--muted);
font-size: 0.98rem;
}
/* ---------- Toggles ---------- */
.toggles {
display: flex;
justify-content: center;
gap: 14px;
flex-wrap: wrap;
}
.toggle-group {
display: inline-flex;
background: var(--panel);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 4px;
gap: 4px;
}
.toggle-btn {
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
background: transparent;
border: 1px solid transparent;
border-radius: var(--r-sm);
padding: 9px 14px;
cursor: pointer;
transition: color 0.18s, background 0.18s, box-shadow 0.18s;
}
.toggle-btn:hover {
color: var(--text);
}
.toggle-btn.is-active {
color: #021018;
background: linear-gradient(135deg, var(--accent), #4dd7ff);
box-shadow: var(--glow);
}
.toggle-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.toggle-tag {
font-family: "Inter", sans-serif;
font-weight: 600;
font-size: 0.62rem;
letter-spacing: 0.02em;
text-transform: none;
opacity: 0.8;
}
/* ---------- Table shell ---------- */
.compare {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: var(--r-lg);
background: linear-gradient(180deg, var(--panel) 0%, var(--bg-2) 100%);
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(231, 233, 243, 0.05);
}
.compare-table {
width: 100%;
min-width: 760px;
border-collapse: collapse;
table-layout: fixed;
}
.col-feature {
width: 30%;
}
/* column hover highlight (driven by JS class on <col>) */
col.is-hovered {
background: rgba(0, 229, 255, 0.06);
}
col[data-col="ultimate"] {
background: linear-gradient(180deg, rgba(124, 77, 255, 0.1), rgba(124, 77, 255, 0.04));
}
col[data-col="ultimate"].is-hovered {
background: linear-gradient(180deg, rgba(124, 77, 255, 0.18), rgba(0, 229, 255, 0.08));
}
/* ---------- Edition headers ---------- */
.edition-head,
.feature-head {
padding: 18px 12px 14px;
vertical-align: bottom;
border-bottom: 1px solid var(--line-2);
}
.edition-card {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
text-align: center;
padding: 12px 8px 10px;
border-radius: var(--r-md);
}
.edition-head.is-ultimate .edition-card {
background: linear-gradient(180deg, rgba(124, 77, 255, 0.18), rgba(124, 77, 255, 0.05));
border: 1px solid rgba(124, 77, 255, 0.55);
box-shadow: 0 0 22px rgba(124, 77, 255, 0.3), inset 0 1px 0 rgba(231, 233, 243, 0.08);
}
.best-value {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 0.6rem;
letter-spacing: 0.14em;
text-transform: uppercase;
white-space: nowrap;
color: #150a2e;
background: linear-gradient(135deg, var(--warn), #ffe29a);
padding: 4px 12px;
clip-path: polygon(8px 0, calc(100% - 8px) 0, 100% 50%, calc(100% - 8px) 100%, 8px 100%, 0 50%);
animation: badge-pulse 2.4s ease-in-out infinite;
}
@keyframes badge-pulse {
0%, 100% { filter: drop-shadow(0 0 4px rgba(255, 200, 87, 0.4)); }
50% { filter: drop-shadow(0 0 12px rgba(255, 200, 87, 0.85)); }
}
.edition-art {
width: 100%;
height: 84px;
border-radius: var(--r-sm);
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%);
display: grid;
place-items: center;
position: relative;
overflow: hidden;
border: 1px solid var(--line-2);
}
.edition-art::after {
content: "";
position: absolute;
inset: 0;
background:
repeating-linear-gradient(115deg, rgba(231, 233, 243, 0.05) 0 2px, transparent 2px 14px);
}
.art-standard {
background: linear-gradient(135deg, #1c2233, #10131f);
}
.art-deluxe {
background: linear-gradient(135deg, #07313c, #0a1722 55%, #102a3f);
}
.art-ultimate {
background: linear-gradient(135deg, #2a1357, #131027 55%, #3c1040);
}
.art-sigil {
font-family: "Orbitron", sans-serif;
font-weight: 900;
font-size: 1.8rem;
letter-spacing: 0.2em;
color: rgba(231, 233, 243, 0.9);
text-shadow: 0 0 18px rgba(0, 229, 255, 0.6);
z-index: 1;
}
.art-ultimate .art-sigil {
text-shadow: 0 0 18px rgba(124, 77, 255, 0.9), 0 0 36px rgba(255, 61, 113, 0.5);
}
.edition-name {
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 1.02rem;
letter-spacing: 0.12em;
text-transform: uppercase;
margin: 4px 0 0;
}
.edition-tag {
margin: 0;
font-size: 0.74rem;
font-weight: 500;
color: var(--muted);
}
.edition-price {
margin: 6px 0 0;
display: flex;
align-items: baseline;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
.price-was {
color: var(--muted);
font-size: 0.8rem;
text-decoration: line-through;
text-decoration-color: var(--accent-3);
}
.price-now {
font-family: "Orbitron", sans-serif;
font-weight: 900;
font-size: 1.45rem;
letter-spacing: 0.02em;
transition: text-shadow 0.25s;
}
.price-now.is-flash {
text-shadow: 0 0 16px rgba(0, 229, 255, 0.9);
}
.price-per {
font-size: 0.72rem;
font-weight: 600;
color: var(--muted);
}
.savings-badge {
margin: 0;
font-size: 0.68rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--success);
border: 1px solid rgba(54, 226, 122, 0.45);
background: rgba(54, 226, 122, 0.1);
border-radius: 999px;
padding: 3px 10px;
}
.savings-badge.is-hot {
color: #0b1a10;
background: var(--success);
border-color: var(--success);
box-shadow: 0 0 14px rgba(54, 226, 122, 0.5);
}
/* ---------- Buy buttons ---------- */
.buy-btn {
margin-top: 8px;
width: 100%;
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 0.78rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text);
background: var(--panel-2);
border: 1px solid var(--line-2);
padding: 12px 10px;
cursor: pointer;
clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
transition: transform 0.15s, box-shadow 0.2s, border-color 0.2s, background 0.2s;
}
.buy-btn:hover {
border-color: var(--accent);
box-shadow: var(--glow);
transform: translateY(-1px);
}
.buy-btn:active {
transform: translateY(1px);
}
.buy-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
.buy-btn.is-primary {
color: #f5f2ff;
background: linear-gradient(135deg, var(--accent-2), #5a2ee0 55%, var(--accent-3));
border-color: rgba(124, 77, 255, 0.8);
box-shadow: 0 0 18px rgba(124, 77, 255, 0.45);
}
.buy-btn.is-primary:hover {
box-shadow: 0 0 26px rgba(124, 77, 255, 0.75), 0 0 40px rgba(255, 61, 113, 0.3);
}
/* ---------- Body rows ---------- */
.compare-table tbody th,
.compare-table tbody td {
padding: 13px 14px;
border-bottom: 1px solid var(--line);
font-size: 0.86rem;
}
.compare-table tbody th[scope="row"] {
text-align: left;
font-weight: 500;
color: var(--text);
}
.compare-table tbody th[scope="row"] em {
color: var(--accent);
font-style: normal;
font-weight: 600;
}
.compare-table tbody td {
text-align: center;
}
.compare-table tbody tr:not(.row-section):hover {
background: rgba(231, 233, 243, 0.03);
}
/* section header rows */
.row-section th {
padding: 0;
background: rgba(231, 233, 243, 0.03);
border-bottom: 1px solid var(--line-2);
}
.section-toggle {
width: 100%;
display: flex;
align-items: center;
gap: 10px;
background: transparent;
border: 0;
color: var(--text);
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 0.72rem;
letter-spacing: 0.18em;
text-transform: uppercase;
text-align: left;
padding: 12px 14px;
cursor: pointer;
transition: color 0.18s, background 0.18s;
}
.section-toggle:hover {
color: var(--accent);
background: rgba(0, 229, 255, 0.05);
}
.section-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.section-caret {
color: var(--accent);
display: inline-block;
width: 1em;
}
/* ---------- Marks ---------- */
.mark {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 26px;
height: 26px;
padding: 0 8px;
border-radius: 999px;
font-weight: 800;
font-size: 0.78rem;
}
.mark.yes {
color: var(--success);
background: rgba(54, 226, 122, 0.12);
text-shadow: 0 0 10px rgba(54, 226, 122, 0.7);
}
.mark.no {
color: rgba(255, 77, 77, 0.75);
background: rgba(255, 77, 77, 0.08);
}
.mark.partial {
color: var(--warn);
background: rgba(255, 200, 87, 0.12);
font-size: 0.7rem;
letter-spacing: 0.03em;
}
/* ---------- Fine print ---------- */
.fineprint {
margin-top: 18px;
text-align: center;
}
.fineprint p {
margin: 0 auto;
max-width: 70ch;
font-size: 0.74rem;
color: var(--muted);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 16px);
background: var(--panel-2);
color: var(--text);
border: 1px solid var(--accent);
box-shadow: var(--glow);
border-radius: var(--r-md);
padding: 12px 18px;
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 0.74rem;
letter-spacing: 0.08em;
text-transform: uppercase;
opacity: 0;
pointer-events: none;
transition: opacity 0.25s, transform 0.25s;
z-index: 50;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 820px) {
.compare-table {
min-width: 640px;
}
.col-feature {
width: 26%;
}
}
@media (max-width: 520px) {
.page {
padding: 32px 12px 56px;
}
.hero-sub {
font-size: 0.88rem;
}
.toggle-btn {
padding: 8px 10px;
font-size: 0.66rem;
}
.compare-table {
min-width: 560px;
}
.edition-art {
height: 60px;
}
.price-now {
font-size: 1.15rem;
}
.buy-btn {
font-size: 0.66rem;
padding: 10px 6px;
}
.compare-table tbody th,
.compare-table tbody td {
padding: 10px 8px;
font-size: 0.78rem;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}(() => {
"use strict";
/* ---------- Data ---------- */
const PRICES_USD = {
standard: { now: 49.99, was: null },
deluxe: { now: 69.99, was: 84.99 },
ultimate: { now: 99.99, was: 134.99 },
};
const CURRENCIES = {
USD: { symbol: "$", rate: 1 },
EUR: { symbol: "€", rate: 0.92 },
GBP: { symbol: "£", rate: 0.79 },
};
const MONTHLY_INSTALLMENTS = 4;
const state = { plan: "once", currency: "USD" };
/* ---------- Toast ---------- */
const toastEl = document.getElementById("toast");
let toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-visible"), 2400);
}
/* ---------- Price rendering ---------- */
function fmt(amountUsd) {
const { symbol, rate } = CURRENCIES[state.currency];
return symbol + (amountUsd * rate).toFixed(2);
}
function renderPrices() {
const monthly = state.plan === "monthly";
Object.keys(PRICES_USD).forEach((edition) => {
const { now, was } = PRICES_USD[edition];
const nowEl = document.querySelector(`[data-price="${edition}"]`);
const wasEl = document.querySelector(`[data-was="${edition}"]`);
const saveEl = document.querySelector(`[data-save="${edition}"]`);
if (nowEl) {
nowEl.textContent = monthly ? fmt(now / MONTHLY_INSTALLMENTS) : fmt(now);
nowEl.classList.add("is-flash");
setTimeout(() => nowEl.classList.remove("is-flash"), 300);
}
if (wasEl && was != null) {
wasEl.textContent = monthly ? fmt(was / MONTHLY_INSTALLMENTS) : fmt(was);
}
if (saveEl && was != null) {
const saved = was - now;
saveEl.textContent = monthly
? `Save ${fmt(saved / MONTHLY_INSTALLMENTS)}/mo`
: `Save ${fmt(saved)}`;
}
});
document.querySelectorAll("[data-per]").forEach((el) => {
el.textContent = monthly ? `/mo × ${MONTHLY_INSTALLMENTS}` : "";
});
}
/* ---------- Toggle groups (plan + currency) ---------- */
function wireToggleGroup(attr, onChange) {
const buttons = document.querySelectorAll(`[${attr}]`);
buttons.forEach((btn) => {
btn.addEventListener("click", () => {
if (btn.classList.contains("is-active")) return;
buttons.forEach((b) => {
b.classList.toggle("is-active", b === btn);
b.setAttribute("aria-checked", b === btn ? "true" : "false");
});
onChange(btn.getAttribute(attr));
});
});
}
wireToggleGroup("data-plan", (plan) => {
state.plan = plan;
renderPrices();
toast(plan === "monthly" ? "Showing 4-payment plan" : "Showing one-time prices");
});
wireToggleGroup("data-currency", (currency) => {
state.currency = currency;
renderPrices();
toast(`Currency set to ${currency}`);
});
/* ---------- Column hover highlight ---------- */
const cols = document.querySelectorAll("col[data-col]");
function highlightColumn(name) {
cols.forEach((c) => c.classList.toggle("is-hovered", c.dataset.col === name));
}
document.querySelectorAll("th[data-col], td[data-col]").forEach((cell) => {
cell.addEventListener("mouseenter", () => highlightColumn(cell.dataset.col));
cell.addEventListener("mouseleave", () => highlightColumn(null));
});
/* ---------- Expandable section groups ---------- */
document.querySelectorAll(".section-toggle").forEach((btn) => {
btn.addEventListener("click", () => {
const section = btn.dataset.section;
const expanded = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", String(!expanded));
btn.querySelector(".section-caret").textContent = expanded ? "▸" : "▾";
document
.querySelectorAll(`tr[data-group="${section}"]`)
.forEach((row) => (row.hidden = expanded));
});
});
/* ---------- Buy buttons ---------- */
document.querySelectorAll("[data-buy]").forEach((btn) => {
btn.addEventListener("click", () => {
const edition = btn.dataset.buy;
const key = edition.toLowerCase();
const price =
state.plan === "monthly"
? `${fmt(PRICES_USD[key].now / MONTHLY_INSTALLMENTS)}/mo`
: fmt(PRICES_USD[key].now);
toast(`${edition} Edition added — ${price}`);
});
});
/* ---------- Init ---------- */
renderPrices();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hollow Reign — Choose Your Edition</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page">
<header class="hero">
<p class="hero-kicker">Nullforge Studios presents</p>
<h1 class="hero-title">Hollow <span>Reign</span></h1>
<p class="hero-sub">Claim the throne of a dying empire. Pick the edition that matches your conquest — upgrade anytime, keep your progress.</p>
<div class="toggles" role="group" aria-label="Pricing options">
<div class="toggle-group" role="radiogroup" aria-label="Payment plan">
<button class="toggle-btn is-active" type="button" role="radio" aria-checked="true" data-plan="once">One-time</button>
<button class="toggle-btn" type="button" role="radio" aria-checked="false" data-plan="monthly">Monthly <span class="toggle-tag">4 payments</span></button>
</div>
<div class="toggle-group" role="radiogroup" aria-label="Currency">
<button class="toggle-btn is-active" type="button" role="radio" aria-checked="true" data-currency="USD">$ USD</button>
<button class="toggle-btn" type="button" role="radio" aria-checked="false" data-currency="EUR">€ EUR</button>
<button class="toggle-btn" type="button" role="radio" aria-checked="false" data-currency="GBP">£ GBP</button>
</div>
</div>
</header>
<section class="compare" aria-label="Edition comparison">
<table class="compare-table" id="compareTable">
<colgroup>
<col class="col-feature" />
<col data-col="standard" />
<col data-col="deluxe" />
<col data-col="ultimate" />
</colgroup>
<thead>
<tr>
<th class="feature-head" scope="col"><span class="sr-only">Feature</span></th>
<th scope="col" class="edition-head" data-col="standard">
<div class="edition-card">
<div class="edition-art art-standard" aria-hidden="true">
<span class="art-sigil">I</span>
</div>
<h2 class="edition-name">Standard</h2>
<p class="edition-tag">The campaign, pure and brutal.</p>
<p class="edition-price">
<span class="price-now" data-price="standard">$49.99</span>
<span class="price-per" data-per></span>
</p>
<button class="buy-btn" type="button" data-buy="Standard">Buy Standard</button>
</div>
</th>
<th scope="col" class="edition-head" data-col="deluxe">
<div class="edition-card">
<div class="edition-art art-deluxe" aria-hidden="true">
<span class="art-sigil">II</span>
</div>
<h2 class="edition-name">Deluxe</h2>
<p class="edition-tag">Armory, soundtrack, head start.</p>
<p class="edition-price">
<span class="price-was" data-was="deluxe">$84.99</span>
<span class="price-now" data-price="deluxe">$69.99</span>
<span class="price-per" data-per></span>
</p>
<p class="savings-badge" data-save="deluxe">Save $15.00</p>
<button class="buy-btn" type="button" data-buy="Deluxe">Buy Deluxe</button>
</div>
</th>
<th scope="col" class="edition-head is-ultimate" data-col="ultimate">
<div class="edition-card">
<span class="best-value" aria-label="Best value edition">★ Best value</span>
<div class="edition-art art-ultimate" aria-hidden="true">
<span class="art-sigil">III</span>
</div>
<h2 class="edition-name">Ultimate</h2>
<p class="edition-tag">Everything. Forever. First.</p>
<p class="edition-price">
<span class="price-was" data-was="ultimate">$134.99</span>
<span class="price-now" data-price="ultimate">$99.99</span>
<span class="price-per" data-per></span>
</p>
<p class="savings-badge is-hot" data-save="ultimate">Save $35.00</p>
<button class="buy-btn is-primary" type="button" data-buy="Ultimate">Buy Ultimate</button>
</div>
</th>
</tr>
</thead>
<tbody>
<tr class="row-section">
<th colspan="4" scope="rowgroup">
<button class="section-toggle" type="button" aria-expanded="true" data-section="core">
<span class="section-caret" aria-hidden="true">▾</span> Core game
</button>
</th>
</tr>
<tr data-group="core">
<th scope="row">Base game — <em>Hollow Reign</em> campaign</th>
<td data-col="standard"><span class="mark yes" title="Included">✓</span></td>
<td data-col="deluxe"><span class="mark yes" title="Included">✓</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="core">
<th scope="row">Online sieges & co-op (up to 4 players)</th>
<td data-col="standard"><span class="mark yes" title="Included">✓</span></td>
<td data-col="deluxe"><span class="mark yes" title="Included">✓</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="core">
<th scope="row">72-hour early access</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark partial" title="Partial">48h</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr class="row-section">
<th colspan="4" scope="rowgroup">
<button class="section-toggle" type="button" aria-expanded="true" data-section="content">
<span class="section-caret" aria-hidden="true">▾</span> Expansions & season pass
</button>
</th>
</tr>
<tr data-group="content">
<th scope="row">Season Pass — Year One (3 expansions)</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark partial" title="Partial">1 of 3</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="content">
<th scope="row">Expansion: <em>Ashen Vanguard</em></th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark yes" title="Included">✓</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="content">
<th scope="row">All future DLC drops (Year Two+)</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark no" title="Not included">✕</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr class="row-section">
<th colspan="4" scope="rowgroup">
<button class="section-toggle" type="button" aria-expanded="false" data-section="cosmetics">
<span class="section-caret" aria-hidden="true">▸</span> Cosmetics & extras
</button>
</th>
</tr>
<tr data-group="cosmetics" hidden>
<th scope="row">"Neon Drift" weapon skin pack (12 skins)</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark yes" title="Included">✓</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="cosmetics" hidden>
<th scope="row">Exalted "Hollow King" armor set + mount</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark no" title="Not included">✕</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="cosmetics" hidden>
<th scope="row">Digital soundtrack (38 tracks, FLAC)</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark yes" title="Included">✓</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
<tr data-group="cosmetics" hidden>
<th scope="row">Digital artbook + lore codex</th>
<td data-col="standard"><span class="mark no" title="Not included">✕</span></td>
<td data-col="deluxe"><span class="mark partial" title="Partial">Artbook</span></td>
<td data-col="ultimate"><span class="mark yes" title="Included">✓</span></td>
</tr>
</tbody>
</table>
</section>
<footer class="fineprint">
<p>Prices shown for digital editions on PC. Monthly plan splits the total into 4 interest-free payments. Editions can be upgraded later for the price difference.</p>
</footer>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Editions / Bundle Compare Table
A store-page style editions comparison for the fictional Nullforge title Hollow Reign. Three edition columns — Standard, Deluxe, and Ultimate — each get a clipped-corner art tile, an Orbitron price block with strike-through “was” pricing, a pill savings badge, and an angled Buy CTA. The Ultimate column carries a pulsing “Best value” hex badge, a violet glow card, and a gradient primary button so the upsell target is unmistakable.
Below the headers sits a feature matrix split into collapsible row groups (Core game, Expansions & season pass, Cosmetics & extras). Cells use glowing check marks, dimmed crosses, and amber partial chips like “48h” or “1 of 3” to communicate nuance instead of a binary yes/no.
The script wires two toggle groups — one-time vs. a 4-payment monthly plan, and USD/EUR/GBP currency — that recompute every price, “was” value, and savings badge with a brief neon flash. Hovering any cell highlights its whole column via <col> classes, section headers expand or collapse their row groups with proper aria-expanded, and each Buy button fires a toast confirming the edition and current price.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.