Game — Character Detail (stats · lore · abilities)
A dark, neon-lit single-character detail page for a fictional action game: hero banner with CSS-built splash art, faction and rarity badges, animated HP/ATK/DEF/SPD/CRIT stat bars with count-up values, an expandable abilities list with icons and cooldowns, a lore section with read-more, a skins selector that retints the whole page accent theme, synergy picks, and an add-to-team CTA with toast feedback — all in Orbitron-styled HUD chrome with clipped corners and glow states.
MCP
Code
:root {
--bg: #0a0b10;
--bg-2: #12131c;
--panel: #171926;
--panel-2: #1f2233;
--text: #e7e9f3;
--muted: #9aa0bf;
--line: rgba(231, 233, 243, 0.10);
--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;
/* runtime-tinted accent (set by JS) */
--skin: var(--accent);
--skin-rgb: 0, 229, 255;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background:
radial-gradient(1200px 600px at 80% -10%, rgba(var(--skin-rgb), 0.10), transparent 60%),
radial-gradient(900px 500px at -10% 20%, rgba(124, 77, 255, 0.08), transparent 55%),
var(--bg);
color: var(--text);
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background 0.5s ease;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: 18px 18px 64px;
}
/* ---------- HERO ---------- */
.hero {
position: relative;
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
overflow: hidden;
background: linear-gradient(160deg, var(--panel-2), var(--bg-2));
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 26px), calc(100% - 26px) 100%, 0 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 22px 60px rgba(0, 0, 0, 0.5);
}
.hero__bg { position: absolute; inset: 0; z-index: 0; }
.hero__splash {
position: absolute;
inset: 0;
background:
radial-gradient(420px 420px at 78% 30%, rgba(var(--skin-rgb), 0.55), transparent 62%),
conic-gradient(from 200deg at 80% 40%, rgba(var(--skin-rgb), 0.30), rgba(124, 77, 255, 0.22), rgba(255, 61, 113, 0.16), rgba(var(--skin-rgb), 0.30));
filter: saturate(1.1);
opacity: 0.85;
transition: background 0.55s ease, opacity 0.55s ease;
animation: drift 14s ease-in-out infinite alternate;
}
.hero__splash::after {
content: "";
position: absolute;
right: 6%;
top: 50%;
width: 220px;
height: 320px;
transform: translateY(-50%) skewX(-6deg);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(var(--skin-rgb), 0.04));
border: 1px solid rgba(255, 255, 255, 0.10);
clip-path: polygon(20% 0, 100% 8%, 88% 100%, 0 86%);
box-shadow: 0 0 60px rgba(var(--skin-rgb), 0.35);
}
@keyframes drift {
to { transform: translateX(-12px) scale(1.04); }
}
.hero__grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(var(--line) 1px, transparent 1px),
linear-gradient(90deg, var(--line) 1px, transparent 1px);
background-size: 34px 34px;
mask-image: linear-gradient(180deg, transparent, #000 40%, #000 70%, transparent);
opacity: 0.5;
}
.hero__glow {
position: absolute;
inset: auto 0 0 0;
height: 50%;
background: linear-gradient(180deg, transparent, rgba(10, 11, 16, 0.92));
}
.hero__inner {
position: relative;
z-index: 1;
padding: 22px clamp(18px, 4vw, 40px) 26px;
min-height: 320px;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 18px;
}
.back {
align-self: flex-start;
text-decoration: none;
color: var(--muted);
font-size: 13px;
font-weight: 600;
letter-spacing: 0.04em;
padding: 6px 12px;
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(10, 11, 16, 0.55);
backdrop-filter: blur(6px);
transition: 0.18s;
}
.back:hover { color: var(--text); border-color: var(--line-2); }
.back span { color: var(--skin); }
.hero__head {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 18px;
flex-wrap: wrap;
}
.badges { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.badge {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 5px 10px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
}
.badge--faction { background: rgba(124, 77, 255, 0.16); color: #cfc2ff; border-color: rgba(124, 77, 255, 0.4); }
.badge--rarity {
background: linear-gradient(180deg, rgba(var(--skin-rgb), 0.22), rgba(var(--skin-rgb), 0.06));
color: var(--text);
border-color: rgba(var(--skin-rgb), 0.5);
box-shadow: 0 0 14px rgba(var(--skin-rgb), 0.25);
}
.hero__name {
font-family: "Orbitron", sans-serif;
font-weight: 900;
font-size: clamp(30px, 6vw, 56px);
line-height: 1.02;
margin: 0;
letter-spacing: 0.01em;
text-shadow: 0 0 28px rgba(var(--skin-rgb), 0.35);
}
.hero__role { margin: 8px 0 12px; color: var(--muted); font-weight: 500; }
.hero__tags { display: flex; gap: 8px; flex-wrap: wrap; }
.tag {
font-size: 12px;
font-weight: 600;
color: var(--muted);
padding: 4px 10px;
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(255, 255, 255, 0.03);
}
.powerblock {
text-align: right;
padding: 12px 18px;
border: 1px solid rgba(var(--skin-rgb), 0.4);
border-radius: var(--r-md);
background: rgba(10, 11, 16, 0.6);
backdrop-filter: blur(6px);
box-shadow: inset 0 0 24px rgba(var(--skin-rgb), 0.12);
clip-path: polygon(10px 0, 100% 0, 100% 100%, 0 100%, 0 10px);
}
.powerblock__label { display: block; font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.powerblock__num {
display: block;
font-family: "Orbitron", sans-serif;
font-weight: 900;
font-size: clamp(26px, 4vw, 38px);
color: var(--skin);
text-shadow: 0 0 18px rgba(var(--skin-rgb), 0.5);
}
.powerblock__sub { font-size: 11px; color: var(--muted); }
.hero__actions { display: flex; gap: 12px; flex-wrap: wrap; }
/* ---------- BUTTONS ---------- */
.btn {
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 14px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--text);
border: 1px solid var(--line-2);
background: var(--panel);
padding: 13px 22px;
border-radius: var(--r-md);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 0.12s, box-shadow 0.2s, border-color 0.2s, background 0.2s;
clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn .btn__ic { font-size: 16px; }
.btn--primary {
background: linear-gradient(180deg, rgba(var(--skin-rgb), 0.95), rgba(var(--skin-rgb), 0.7));
color: #061015;
border-color: rgba(var(--skin-rgb), 0.7);
box-shadow: 0 0 22px rgba(var(--skin-rgb), 0.45);
}
.btn--primary:hover { box-shadow: 0 0 30px rgba(var(--skin-rgb), 0.7); }
.btn--ghost:hover { border-color: rgba(var(--skin-rgb), 0.6); box-shadow: var(--glow); }
.btn--ghost[aria-pressed="true"] { color: var(--accent-3); border-color: var(--accent-3); }
:focus-visible {
outline: 2px solid var(--skin);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ---------- LAYOUT ---------- */
.layout {
display: grid;
grid-template-columns: 1.7fr 1fr;
gap: 16px;
margin-top: 16px;
}
.col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* ---------- PANELS ---------- */
.panel {
background: linear-gradient(180deg, var(--panel), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 18px;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.panel__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--line);
}
.panel__title {
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 16px;
letter-spacing: 0.05em;
margin: 0;
text-transform: uppercase;
}
.panel__title::before {
content: "";
display: inline-block;
width: 8px; height: 8px;
margin-right: 9px;
background: var(--skin);
box-shadow: 0 0 10px var(--skin);
transform: rotate(45deg);
vertical-align: middle;
}
.panel__hint { font-size: 12px; color: var(--muted); }
/* ---------- STATS ---------- */
.statgrid { display: flex; flex-direction: column; gap: 14px; }
.stat { display: grid; grid-template-columns: 64px 1fr 54px; align-items: center; gap: 12px; }
.stat__label { font-size: 12px; font-weight: 700; letter-spacing: 0.08em; color: var(--muted); text-transform: uppercase; }
.stat__track {
position: relative;
height: 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--line);
overflow: hidden;
clip-path: polygon(4px 0, 100% 0, calc(100% - 4px) 100%, 0 100%);
}
.stat__fill {
position: absolute;
inset: 0 auto 0 0;
width: 0;
border-radius: 999px;
background: linear-gradient(90deg, rgba(var(--skin-rgb), 0.5), var(--skin));
box-shadow: 0 0 12px rgba(var(--skin-rgb), 0.55);
transition: width 1s cubic-bezier(0.16, 1, 0.3, 1);
}
.stat__val {
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 14px;
text-align: right;
color: var(--text);
}
/* ---------- ABILITIES ---------- */
.ablist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.ability {
border: 1px solid var(--line);
border-radius: var(--r-md);
background: rgba(255, 255, 255, 0.02);
overflow: hidden;
transition: border-color 0.2s, box-shadow 0.2s;
}
.ability.is-open { border-color: rgba(var(--skin-rgb), 0.5); box-shadow: 0 0 20px rgba(var(--skin-rgb), 0.14); }
.ability__btn {
width: 100%;
display: grid;
grid-template-columns: 46px 1fr auto;
align-items: center;
gap: 12px;
padding: 12px 14px;
background: none;
border: 0;
color: inherit;
cursor: pointer;
text-align: left;
font: inherit;
}
.ability__icon {
width: 42px; height: 42px;
display: grid;
place-items: center;
font-size: 20px;
border-radius: var(--r-sm);
background: linear-gradient(180deg, rgba(var(--skin-rgb), 0.2), rgba(var(--skin-rgb), 0.05));
border: 1px solid rgba(var(--skin-rgb), 0.35);
box-shadow: inset 0 0 12px rgba(var(--skin-rgb), 0.15);
}
.ability__name { font-weight: 700; font-size: 15px; }
.ability__type { font-size: 11px; color: var(--muted); letter-spacing: 0.05em; text-transform: uppercase; }
.ability__meta { display: flex; align-items: center; gap: 10px; }
.ability__cd {
font-family: "Orbitron", sans-serif;
font-size: 12px;
font-weight: 700;
color: var(--warn);
padding: 4px 8px;
border: 1px solid rgba(255, 200, 87, 0.4);
border-radius: var(--r-sm);
background: rgba(255, 200, 87, 0.08);
white-space: nowrap;
}
.ability__cd[data-cd="0s"] { color: var(--success); border-color: rgba(54, 226, 122, 0.4); background: rgba(54, 226, 122, 0.08); }
.ability__chev { color: var(--muted); transition: transform 0.25s; font-size: 14px; }
.ability.is-open .ability__chev { transform: rotate(180deg); color: var(--skin); }
.ability__body {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.ability__inner { padding: 0 14px 14px 72px; }
.ability__desc { margin: 0 0 10px; color: var(--muted); font-size: 14px; }
.ability__stats { display: flex; gap: 8px; flex-wrap: wrap; }
.ability__chip {
font-size: 11px;
font-weight: 600;
padding: 3px 9px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--line);
color: var(--text);
}
.ability__chip b { color: var(--skin); }
/* ---------- LORE ---------- */
.lore__body { color: var(--muted); }
.lore__body p { margin: 0 0 12px; }
.lore__body p:last-child { margin-bottom: 0; }
.lore__more { display: none; }
.lore__body[data-open="true"] .lore__more { display: block; animation: fade 0.4s ease; }
.lore em { color: var(--text); font-style: normal; font-weight: 600; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } }
.readmore {
margin-top: 12px;
font-family: "Orbitron", sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--skin);
background: none;
border: 1px solid rgba(var(--skin-rgb), 0.4);
border-radius: var(--r-sm);
padding: 8px 14px;
cursor: pointer;
transition: 0.18s;
}
.readmore:hover { background: rgba(var(--skin-rgb), 0.1); box-shadow: var(--glow); }
/* ---------- SKINS ---------- */
.skinrow { display: flex; gap: 10px; flex-wrap: wrap; }
.skinchip {
width: 44px; height: 44px;
display: grid;
place-items: center;
border-radius: var(--r-md);
background: var(--panel-2);
border: 1px solid var(--line);
cursor: pointer;
transition: transform 0.15s, border-color 0.2s, box-shadow 0.2s;
}
.skinchip:hover { transform: translateY(-2px); }
.skinchip__sw {
width: 22px; height: 22px;
border-radius: 6px;
background: var(--sw);
box-shadow: 0 0 12px var(--sw);
}
.skinchip.is-active { border-color: var(--sw); box-shadow: 0 0 0 1px var(--sw), 0 0 16px rgba(255, 255, 255, 0.06); }
.skinchip.is-active .skinchip__sw { transform: scale(1.08); }
.skins__name { margin: 14px 0 0; font-size: 13px; color: var(--muted); }
.skins__name strong { color: var(--text); }
/* ---------- SYNERGY ---------- */
.synlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.synrow {
display: grid;
grid-template-columns: 34px 1fr auto;
align-items: center;
gap: 10px;
padding: 8px 10px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: rgba(255, 255, 255, 0.02);
}
.synrow__av {
width: 34px; height: 34px;
display: grid;
place-items: center;
font-family: "Orbitron", sans-serif;
font-size: 11px;
font-weight: 700;
border-radius: 8px;
background: linear-gradient(180deg, rgba(var(--skin-rgb), 0.25), rgba(var(--skin-rgb), 0.06));
border: 1px solid rgba(var(--skin-rgb), 0.35);
}
.synrow__n { font-size: 14px; font-weight: 600; }
.synrow__b { font-family: "Orbitron", sans-serif; font-size: 12px; font-weight: 700; }
.synrow__b.up { color: var(--success); }
.synrow__b.down { color: var(--accent-3); }
/* ---------- TOASTS ---------- */
.toasts {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 60;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.toast {
pointer-events: auto;
min-width: 220px;
padding: 12px 16px;
border-radius: var(--r-md);
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid rgba(var(--skin-rgb), 0.5);
box-shadow: 0 0 24px rgba(var(--skin-rgb), 0.25), 0 18px 40px rgba(0, 0, 0, 0.5);
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
animation: toastIn 0.28s cubic-bezier(0.16, 1, 0.3, 1);
clip-path: polygon(8px 0, 100% 0, 100% 100%, 0 100%, 0 8px);
}
.toast::before { content: "▸"; color: var(--skin); }
.toast.out { animation: toastOut 0.3s ease forwards; }
@keyframes toastIn { from { opacity: 0; transform: translateX(30px); } }
@keyframes toastOut { to { opacity: 0; transform: translateX(30px); } }
/* ---------- RESPONSIVE ---------- */
@media (max-width: 860px) {
.layout { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.page { padding: 12px 12px 56px; }
.hero__head { flex-direction: column; align-items: flex-start; }
.powerblock { text-align: left; align-self: stretch; }
.hero__actions .btn { flex: 1; justify-content: center; }
.stat { grid-template-columns: 52px 1fr 44px; gap: 8px; }
.ability__inner { padding-left: 14px; }
.toasts { left: 12px; right: 12px; }
.toast { min-width: 0; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}/* Game — Character Detail
Interactions: ability expand/collapse, animated stat bars + counters,
skin selector (re-tints page accent + splash), lore read-more, toasts. */
(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastWrap = document.getElementById("toasts");
function toast(msg) {
if (!toastWrap) return;
var t = document.createElement("div");
t.className = "toast";
t.textContent = msg;
toastWrap.appendChild(t);
setTimeout(function () {
t.classList.add("out");
t.addEventListener("animationend", function () { t.remove(); });
}, 2600);
}
/* ---------- data ---------- */
var STATS = [
{ label: "HP", value: 8420, max: 10000 },
{ label: "ATK", value: 1960, max: 2200 },
{ label: "DEF", value: 1480, max: 2000 },
{ label: "SPD", value: 112, max: 160 },
{ label: "CRIT", value: 68, max: 100 }
];
var ABILITIES = [
{
icon: "⚔", name: "Cleaving Vow", type: "Basic · Melee", cd: "0s",
desc: "A wide arc strike that hits all enemies in front of the Vanguard, dealing 180% ATK physical damage and generating 1 Resolve per target struck.",
chips: [["Damage", "180% ATK"], ["Targets", "All front"], ["Gen", "+1 Resolve"]]
},
{
icon: "🛡", name: "Iron Bulwark", type: "Skill · Defensive", cd: "8s",
desc: "Raises a fractured aegis for 6s, reducing damage taken by 40% and taunting the nearest 3 foes. Allies behind the Vanguard gain 15% DEF.",
chips: [["DR", "40%"], ["Duration", "6s"], ["Ally DEF", "+15%"]]
},
{
icon: "✶", name: "Ash Detonation", type: "Ultimate · Burst", cd: "22s",
desc: "Consumes all Resolve to slam the ground, dealing 90% ATK per stack as area damage and stunning struck enemies for 1.5s. Heals the Vanguard for 30% of damage dealt.",
chips: [["Damage", "90% / stack"], ["Stun", "1.5s"], ["Lifesteal", "30%"]]
},
{
icon: "♾", name: "Last Warden", type: "Passive", cd: "—",
desc: "While below 35% HP, the Vanguard cannot be reduced below 1 HP for 3s (once per battle) and gains 25% increased ATK until the effect ends.",
chips: [["Trigger", "<35% HP"], ["Guard", "3s"], ["ATK", "+25%"]]
}
];
/* ---------- render stats ---------- */
var statgrid = document.getElementById("statgrid");
var statEls = [];
STATS.forEach(function (s) {
var pct = Math.round((s.value / s.max) * 100);
var row = document.createElement("div");
row.className = "stat";
row.innerHTML =
'<span class="stat__label">' + s.label + '</span>' +
'<span class="stat__track"><span class="stat__fill"></span></span>' +
'<span class="stat__val">0</span>';
statgrid.appendChild(row);
statEls.push({
fill: row.querySelector(".stat__fill"),
val: row.querySelector(".stat__val"),
pct: pct,
target: s.value
});
});
function countUp(el, target, dur) {
var start = performance.now();
function step(now) {
var t = Math.min((now - start) / dur, 1);
var eased = 1 - Math.pow(1 - t, 3);
el.textContent = Math.round(target * eased).toLocaleString("en-US");
if (t < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
function animateStats() {
statEls.forEach(function (s, i) {
setTimeout(function () {
s.fill.style.width = s.pct + "%";
countUp(s.val, s.target, 1000);
}, 120 * i);
});
}
// animate when stats panel scrolls into view (or immediately if already visible)
var fired = false;
function maybeAnimate() {
if (fired) return;
fired = true;
animateStats();
}
if ("IntersectionObserver" in window) {
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) { maybeAnimate(); io.disconnect(); } });
}, { threshold: 0.3 });
io.observe(statgrid);
} else {
maybeAnimate();
}
// safety: fire shortly after load regardless
window.addEventListener("load", function () { setTimeout(maybeAnimate, 400); });
/* ---------- render abilities ---------- */
var ablist = document.getElementById("ablist");
ABILITIES.forEach(function (a, idx) {
var li = document.createElement("li");
li.className = "ability";
var bodyId = "ab-body-" + idx;
var chips = a.chips.map(function (c) {
return '<span class="ability__chip">' + c[0] + ' <b>' + c[1] + '</b></span>';
}).join("");
li.innerHTML =
'<button class="ability__btn" aria-expanded="false" aria-controls="' + bodyId + '">' +
'<span class="ability__icon" aria-hidden="true">' + a.icon + '</span>' +
'<span><span class="ability__name">' + a.name + '</span><br>' +
'<span class="ability__type">' + a.type + '</span></span>' +
'<span class="ability__meta">' +
'<span class="ability__cd" data-cd="' + a.cd + '">' + (a.cd === "—" ? "Passive" : "CD " + a.cd) + '</span>' +
'<span class="ability__chev" aria-hidden="true">▾</span>' +
'</span>' +
'</button>' +
'<div class="ability__body" id="' + bodyId + '"><div class="ability__inner">' +
'<p class="ability__desc">' + a.desc + '</p>' +
'<div class="ability__stats">' + chips + '</div>' +
'</div></div>';
ablist.appendChild(li);
});
ablist.addEventListener("click", function (e) {
var btn = e.target.closest(".ability__btn");
if (!btn) return;
var ability = btn.closest(".ability");
var body = ability.querySelector(".ability__body");
var open = ability.classList.toggle("is-open");
btn.setAttribute("aria-expanded", String(open));
body.style.maxHeight = open ? body.scrollHeight + "px" : "0px";
});
/* ---------- read more ---------- */
var readMore = document.getElementById("readMore");
var loreBody = document.getElementById("loreBody");
readMore.addEventListener("click", function () {
var open = loreBody.getAttribute("data-open") !== "true";
loreBody.setAttribute("data-open", String(open));
readMore.setAttribute("aria-expanded", String(open));
readMore.textContent = open ? "Read less" : "Read more";
});
/* ---------- skin selector ---------- */
function hexToRgb(hex) {
var h = hex.replace("#", "");
if (h.length === 3) h = h.split("").map(function (c) { return c + c; }).join("");
var n = parseInt(h, 16);
return (n >> 16 & 255) + ", " + (n >> 8 & 255) + ", " + (n & 255);
}
var skinrow = document.getElementById("skinrow");
var skinName = document.getElementById("skinName");
var root = document.documentElement;
skinrow.addEventListener("click", function (e) {
var chip = e.target.closest(".skinchip");
if (!chip) return;
var accent = chip.getAttribute("data-accent");
var label = chip.getAttribute("data-label");
root.style.setProperty("--skin", accent);
root.style.setProperty("--skin-rgb", hexToRgb(accent));
document.querySelector(".page").setAttribute("data-skin", chip.getAttribute("data-skin"));
skinrow.querySelectorAll(".skinchip").forEach(function (c) {
var on = c === chip;
c.classList.toggle("is-active", on);
c.setAttribute("aria-selected", String(on));
});
if (skinName) skinName.textContent = label;
toast("Skin equipped · " + label);
});
/* keyboard nav for the skin tablist */
skinrow.addEventListener("keydown", function (e) {
if (e.key !== "ArrowRight" && e.key !== "ArrowLeft") return;
var chips = Array.prototype.slice.call(skinrow.querySelectorAll(".skinchip"));
var i = chips.indexOf(document.activeElement);
if (i === -1) return;
e.preventDefault();
var next = e.key === "ArrowRight" ? (i + 1) % chips.length : (i - 1 + chips.length) % chips.length;
chips[next].focus();
chips[next].click();
});
/* ---------- CTAs ---------- */
var inTeam = false;
var addTeam = document.getElementById("addTeam");
addTeam.addEventListener("click", function () {
inTeam = !inTeam;
addTeam.innerHTML = inTeam
? '<span class="btn__ic" aria-hidden="true">✓</span> In Team'
: '<span class="btn__ic" aria-hidden="true">+</span> Add to Team';
toast(inTeam ? "Ashen Vanguard added to your team" : "Removed from team");
});
var wishlist = document.getElementById("wishlist");
wishlist.addEventListener("click", function () {
var on = wishlist.getAttribute("aria-pressed") !== "true";
wishlist.setAttribute("aria-pressed", String(on));
wishlist.innerHTML = on
? '<span class="btn__ic" aria-hidden="true">♥</span> Wishlisted'
: '<span class="btn__ic" aria-hidden="true">♡</span> Wishlist';
toast(on ? "Saved to wishlist" : "Removed from wishlist");
});
document.querySelector(".back").addEventListener("click", function (e) {
e.preventDefault();
toast("Returning to roster…");
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ashen Vanguard — Character Detail</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" data-skin="default">
<!-- HERO -->
<section class="hero" aria-labelledby="char-name">
<div class="hero__bg" aria-hidden="true">
<div class="hero__splash" id="splash"></div>
<div class="hero__grid"></div>
<div class="hero__glow"></div>
</div>
<div class="hero__inner">
<a class="back" href="#" aria-label="Back to roster">
<span aria-hidden="true">‹</span> Roster
</a>
<div class="hero__head">
<div class="hero__titles">
<div class="badges">
<span class="badge badge--faction" id="faction">Nullforge Syndicate</span>
<span class="badge badge--rarity" id="rarity" data-rarity="legendary">★★★★★ Legendary</span>
</div>
<h1 class="hero__name" id="char-name">Ashen Vanguard</h1>
<p class="hero__role" id="char-title">The Hollow Reign · Vanguard / Bruiser</p>
<div class="hero__tags" role="list">
<span role="listitem" class="tag">Melee</span>
<span role="listitem" class="tag">Frontline</span>
<span role="listitem" class="tag">Burst</span>
</div>
</div>
<div class="powerblock" aria-label="Power rating">
<span class="powerblock__label">Power</span>
<span class="powerblock__num" id="power">9 240</span>
<span class="powerblock__sub">Tier S · Combat ready</span>
</div>
</div>
<div class="hero__actions">
<button class="btn btn--primary" id="addTeam">
<span class="btn__ic" aria-hidden="true">+</span> Add to Team
</button>
<button class="btn btn--ghost" id="wishlist" aria-pressed="false">
<span class="btn__ic" aria-hidden="true">♡</span> Wishlist
</button>
</div>
</div>
</section>
<div class="layout">
<!-- LEFT COLUMN -->
<div class="col col--main">
<!-- STATS -->
<section class="panel stats" aria-labelledby="stats-h">
<header class="panel__head">
<h2 class="panel__title" id="stats-h">Combat Stats</h2>
<span class="panel__hint">Lv. 80 · Awakened</span>
</header>
<div class="statgrid" id="statgrid">
<!-- injected by JS -->
</div>
</section>
<!-- ABILITIES -->
<section class="panel abilities" aria-labelledby="ab-h">
<header class="panel__head">
<h2 class="panel__title" id="ab-h">Abilities</h2>
<span class="panel__hint">Tap a skill to expand</span>
</header>
<ul class="ablist" id="ablist">
<!-- injected by JS -->
</ul>
</section>
<!-- LORE -->
<section class="panel lore" aria-labelledby="lore-h">
<header class="panel__head">
<h2 class="panel__title" id="lore-h">Lore & Backstory</h2>
<span class="panel__hint">Codex · Vol. III</span>
</header>
<div class="lore__body" id="loreBody" data-open="false">
<p>
Forged in the ash-wastes beyond the Severed Gate, the Vanguard was the last warden of
a city that no longer exists. When the Hollow Reign swallowed the northern spires,
they walked back out of the smoke wearing armor fused to bone — alive only by refusing
to fall.
</p>
<p class="lore__more">
Nullforge recovered the Vanguard from a collapsed reliquary, half-buried and still
breathing static. Today they march at the front of every assault, a wall of grey iron
that the enemy learns to fear before they ever see a face. The Vanguard speaks rarely;
when they do, it is only to count the seconds until the next charge.
</p>
<p class="lore__more">
Field operatives report that the Vanguard's presence steadies an entire squad —
morale climbs, hesitation fades. Whether that is tactics or something older, the
archives do not say. They simply note: <em>where the Vanguard holds, the line holds.</em>
</p>
</div>
<button class="readmore" id="readMore" aria-expanded="false">Read more</button>
</section>
</div>
<!-- RIGHT COLUMN -->
<aside class="col col--side">
<!-- SKINS -->
<section class="panel skins" aria-labelledby="skins-h">
<header class="panel__head">
<h2 class="panel__title" id="skins-h">Skins & Variants</h2>
</header>
<div class="skinrow" id="skinrow" role="tablist" aria-label="Skin selector">
<button class="skinchip is-active" role="tab" aria-selected="true" data-skin="default" data-accent="#00e5ff" data-label="Ashfall (default)">
<span class="skinchip__sw" style="--sw:#00e5ff"></span>
</button>
<button class="skinchip" role="tab" aria-selected="false" data-skin="violet" data-accent="#7c4dff" data-label="Void Reign">
<span class="skinchip__sw" style="--sw:#7c4dff"></span>
</button>
<button class="skinchip" role="tab" aria-selected="false" data-skin="rose" data-accent="#ff3d71" data-label="Crimson Oath">
<span class="skinchip__sw" style="--sw:#ff3d71"></span>
</button>
<button class="skinchip" role="tab" aria-selected="false" data-skin="gold" data-accent="#ffc857" data-label="Gilded Warden">
<span class="skinchip__sw" style="--sw:#ffc857"></span>
</button>
<button class="skinchip" role="tab" aria-selected="false" data-skin="mint" data-accent="#36e27a" data-label="Verdant Echo">
<span class="skinchip__sw" style="--sw:#36e27a"></span>
</button>
</div>
<p class="skins__name">Skin: <strong id="skinName">Ashfall (default)</strong></p>
</section>
<!-- SYNERGY -->
<section class="panel synergy" aria-labelledby="syn-h">
<header class="panel__head">
<h2 class="panel__title" id="syn-h">Team Synergy</h2>
</header>
<ul class="synlist">
<li class="synrow"><span class="synrow__av">NX</span><span class="synrow__n">Neon Drift</span><span class="synrow__b up">+12% SPD</span></li>
<li class="synrow"><span class="synrow__av">HR</span><span class="synrow__n">Hollow Reign</span><span class="synrow__b up">+8% ATK</span></li>
<li class="synrow"><span class="synrow__av">SV</span><span class="synrow__n">Silent Vow</span><span class="synrow__b down">−5% CD</span></li>
</ul>
</section>
</aside>
</div>
</main>
<div class="toasts" id="toasts" aria-live="polite" aria-atomic="false"></div>
<script src="script.js"></script>
</body>
</html>Character Detail (stats · lore · abilities)
A full character detail page for “Ashen Vanguard”, a legendary frontliner from the fictional game Hollow Reign by studio Nullforge. The hero banner pairs pure-CSS splash art (a drifting conic glow, an angled clip-path figure, and a faint HUD grid) with the character’s name, title, faction badge, five-star rarity row, role chips, and a glowing Power rating block. Two angled-corner CTAs — “Add to Team” and “Wishlist” — anchor the banner, each with toast feedback.
The body splits into a main column and a sidebar. Combat stats render as clipped bars that animate to their fill percentage while the numeric values count up the first time the panel scrolls into view. Abilities render as expandable rows with glyph icons, cooldown readouts, and detail regions containing damage numbers and meta pills, driven by aria-expanded and a measured max-height transition. The lore panel hides its extra paragraphs behind a read-more toggle.
The sidebar holds a skins selector built as an ARIA tablist: choosing a variant updates the --skin and --skin-rgb custom properties at the document root, retinting the accent, glow, splash art, badges, bars, and buttons across the entire page in one move (arrow keys move between swatches). A small toast() helper surfaces every action, all controls are keyboard-usable with visible focus rings, and the layout collapses to a single column and holds together down to roughly 360px wide.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.