Game — Fantasy RPG Landing
An epic fantasy RPG landing page for the fictional Ashen Vanguard by studio Nullforge, built in HTML, CSS, and vanilla JavaScript with an illuminated-manuscript feel. A parchment-and-gold hero frames a CSS-drawn vista of mountains and a ruined castle under an ember sky, with a Cinzel title and lore tagline. Below it sit a classes-and-bloodlines showcase with crests and animated stat bars, an unfurling lore-scroll accordion, a factions band, and an ornate gold-filigree footer. Scroll-reveal gold shimmer and a wishlist toggle bring it to life.
MCP
Code
:root {
--bg: #1a130c;
--bg-2: #120d07;
--panel: #241a10;
--panel-2: #2e2114;
--text: #f3e9d2;
--muted: #b7a589;
--accent: #e8c15a; /* gold */
--accent-deep: #b8902f;
--accent-2: #9b1b30; /* crimson */
--accent-2-deep: #6e1020;
--parchment: #efe3c6;
--line: rgba(232, 193, 90, 0.22);
--line-2: rgba(232, 193, 90, 0.45);
--glow: 0 0 22px rgba(232, 193, 90, 0.40);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background:
radial-gradient(1200px 600px at 50% -10%, rgba(232,193,90,0.10), transparent 60%),
linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 100%);
color: var(--text);
font-family: "EB Garamond", Georgia, "Times New Roman", serif;
font-size: 18px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
h1, h2, h3, .eyebrow, .btn, .brand__name, .brand__game {
font-family: "Cinzel", Georgia, serif;
}
a { color: inherit; }
.skip {
position: absolute;
left: -9999px;
top: 0;
background: var(--accent);
color: #1a130c;
padding: 10px 16px;
border-radius: var(--r-sm);
z-index: 50;
}
.skip:focus { left: 12px; top: 12px; }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
border-radius: var(--r-sm);
}
/* ---------- filigree helper ---------- */
.filigree {
border: 1px solid var(--line-2);
box-shadow:
inset 0 0 0 1px rgba(255,255,255,0.03),
inset 0 0 30px rgba(232,193,90,0.05);
}
/* ---------- topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 30;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px clamp(16px, 4vw, 48px);
background: linear-gradient(180deg, rgba(18,13,7,0.92), rgba(18,13,7,0.70));
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 10px; }
.crest {
color: var(--accent);
font-size: 1.1rem;
text-shadow: var(--glow);
}
.brand__name { font-weight: 700; letter-spacing: 0.14em; font-size: 0.95rem; text-transform: uppercase; }
.brand__sep { color: var(--muted); }
.brand__game { color: var(--accent); font-weight: 700; letter-spacing: 0.10em; font-size: 0.9rem; }
.nav { display: flex; align-items: center; gap: clamp(10px, 2vw, 26px); }
.nav > a {
text-decoration: none;
color: var(--muted);
font-family: "Cinzel", serif;
font-size: 0.85rem;
letter-spacing: 0.08em;
text-transform: uppercase;
transition: color 0.2s ease;
}
.nav > a:hover { color: var(--accent); }
/* ---------- buttons ---------- */
.btn {
--pad-y: 14px;
display: inline-flex;
align-items: center;
gap: 9px;
padding: var(--pad-y) 26px;
font-size: 0.92rem;
font-weight: 700;
letter-spacing: 0.10em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
border: 1px solid transparent;
border-radius: var(--r-sm);
transition: transform 0.15s ease, box-shadow 0.25s ease, background 0.25s ease, color 0.2s ease;
background: none;
color: var(--text);
}
.btn:active { transform: translateY(1px) scale(0.99); }
.btn--sm { --pad-y: 9px; padding: 9px 16px; font-size: 0.78rem; }
.btn--gold {
color: #1a130c;
background: linear-gradient(180deg, #f4d780, var(--accent) 45%, var(--accent-deep));
border-color: rgba(255,255,255,0.35);
box-shadow: var(--glow), inset 0 1px 0 rgba(255,255,255,0.5);
}
.btn--gold:hover { box-shadow: 0 0 32px rgba(232,193,90,0.65), inset 0 1px 0 rgba(255,255,255,0.5); transform: translateY(-2px); }
.btn--outline {
color: var(--accent);
border-color: var(--line-2);
background: rgba(232,193,90,0.04);
}
.btn--outline:hover { border-color: var(--accent); box-shadow: var(--glow); color: var(--text); }
.btn--ghost {
color: var(--muted);
border-color: var(--line);
}
.btn--ghost:hover { color: var(--accent); border-color: var(--line-2); }
.btn[data-wishlist][aria-pressed="true"] {
color: #1a130c;
background: linear-gradient(180deg, #f4d780, var(--accent) 60%, var(--accent-deep));
border-color: rgba(255,255,255,0.35);
box-shadow: var(--glow);
}
.btn[data-wishlist] .wl-icon { transition: transform 0.25s ease; }
.btn[data-wishlist][aria-pressed="true"] .wl-icon { transform: scale(1.25); color: var(--accent-2); }
.rune { color: inherit; }
/* ---------- HERO ---------- */
.hero {
position: relative;
min-height: clamp(620px, 88vh, 880px);
display: grid;
place-items: center;
padding: clamp(40px, 8vh, 90px) clamp(16px, 5vw, 48px);
overflow: hidden;
isolation: isolate;
}
.vista { position: absolute; inset: 0; z-index: -2; }
.vista__sky {
position: absolute; inset: 0;
background:
linear-gradient(180deg, #2a1d10 0%, #43260f 38%, #7a3a1c 62%, #b8662b 80%, #d9913f 100%);
}
.vista__sun {
position: absolute;
left: 50%; bottom: 16%;
width: 240px; height: 240px;
transform: translateX(-50%);
border-radius: 50%;
background: radial-gradient(circle, #ffe6a8 0%, #f4c45e 35%, rgba(232,193,90,0.15) 70%, transparent 75%);
filter: blur(2px);
}
.vista__mountains {
position: absolute;
left: -5%; right: -5%; bottom: 0;
height: 60%;
}
.vista__mountains.m3 {
bottom: 14%;
height: 42%;
background: linear-gradient(180deg, #5a3417, #3a2110);
clip-path: polygon(0 100%, 8% 52%, 20% 78%, 33% 38%, 48% 70%, 62% 30%, 76% 66%, 90% 44%, 100% 80%, 100% 100%);
opacity: 0.7;
}
.vista__mountains.m2 {
bottom: 6%;
height: 50%;
background: linear-gradient(180deg, #432312, #2a1409);
clip-path: polygon(0 100%, 12% 44%, 26% 70%, 40% 30%, 55% 64%, 70% 22%, 84% 58%, 100% 36%, 100% 100%);
opacity: 0.85;
}
.vista__mountains.m1 {
height: 40%;
background: linear-gradient(180deg, #261307, #140a04);
clip-path: polygon(0 100%, 14% 56%, 30% 80%, 46% 46%, 60% 74%, 74% 40%, 88% 70%, 100% 50%, 100% 100%);
}
.vista__castle {
position: absolute;
left: 50%; bottom: 20%;
transform: translateX(-50%);
width: 220px; height: 150px;
display: flex; align-items: flex-end; justify-content: center;
gap: 4px;
filter: drop-shadow(0 0 18px rgba(0,0,0,0.5));
opacity: 0.92;
}
.vista__castle .tower, .vista__castle .keep {
background: linear-gradient(180deg, #2a1809, #160c04);
display: block;
}
.vista__castle .tower {
width: 26px; height: 96px;
clip-path: polygon(0 18%, 18% 18%, 18% 0, 32% 12%, 50% 0, 68% 12%, 82% 0, 82% 18%, 100% 18%, 100% 100%, 0 100%);
}
.vista__castle .t1 { height: 78px; }
.vista__castle .t3 { height: 86px; }
.vista__castle .keep {
width: 78px; height: 124px;
clip-path: polygon(0 22%, 14% 22%, 14% 8%, 28% 18%, 42% 8%, 50% 0, 58% 8%, 72% 18%, 86% 8%, 86% 22%, 100% 22%, 100% 100%, 0 100%);
}
.vista__mist {
position: absolute; left: 0; right: 0; bottom: 0;
height: 38%;
background: linear-gradient(180deg, transparent, rgba(26,19,12,0.55) 55%, var(--bg) 100%);
}
.hero__frame {
position: relative;
max-width: 720px;
text-align: center;
padding: clamp(28px, 5vw, 52px) clamp(22px, 5vw, 56px);
background:
linear-gradient(180deg, rgba(36,26,16,0.86), rgba(18,13,7,0.92));
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
box-shadow:
0 30px 80px rgba(0,0,0,0.55),
inset 0 0 0 1px rgba(232,193,90,0.10),
inset 0 0 60px rgba(232,193,90,0.06);
}
/* ornate corner filigree */
.hero__frame::before,
.hero__frame::after {
content: "";
position: absolute;
width: 46px; height: 46px;
border: 2px solid var(--accent);
opacity: 0.7;
pointer-events: none;
}
.hero__frame::before { top: 10px; left: 10px; border-right: 0; border-bottom: 0; border-radius: 12px 0 0 0; }
.hero__frame::after { bottom: 10px; right: 10px; border-left: 0; border-top: 0; border-radius: 0 0 12px 0; }
.eyebrow {
margin: 0 0 12px;
font-family: "Cinzel", serif;
font-size: 0.78rem;
letter-spacing: 0.30em;
text-transform: uppercase;
color: var(--accent);
}
.hero__title {
margin: 0;
font-size: clamp(2.6rem, 8vw, 5rem);
font-weight: 900;
line-height: 1.02;
letter-spacing: 0.04em;
background: linear-gradient(180deg, #fdf0c8 0%, var(--accent) 55%, var(--accent-deep) 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 2px 30px rgba(232,193,90,0.25);
}
.hero__lore {
margin: 18px auto 0;
max-width: 56ch;
color: var(--parchment);
font-size: 1.12rem;
}
.hero__cta {
margin-top: 26px;
display: flex;
flex-wrap: wrap;
gap: 14px;
justify-content: center;
}
.hero__meta {
margin: 30px 0 0;
padding: 18px 0 0;
border-top: 1px solid var(--line);
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 10px clamp(18px, 4vw, 40px);
justify-content: center;
color: var(--muted);
font-size: 0.92rem;
}
.hero__meta strong {
display: block;
font-family: "Cinzel", serif;
color: var(--accent);
letter-spacing: 0.06em;
font-size: 0.82rem;
text-transform: uppercase;
}
/* ---------- sections ---------- */
.section {
max-width: 1180px;
margin: 0 auto;
padding: clamp(56px, 9vw, 110px) clamp(16px, 5vw, 48px);
}
.section--lore {
background:
radial-gradient(900px 400px at 50% 0%, rgba(155,27,48,0.10), transparent 60%);
}
.section__head { text-align: center; max-width: 640px; margin: 0 auto clamp(36px, 6vw, 60px); }
.section__title {
margin: 6px 0 0;
font-size: clamp(1.9rem, 5vw, 3rem);
font-weight: 700;
letter-spacing: 0.03em;
color: var(--text);
}
.section__sub { margin: 14px 0 0; color: var(--muted); font-size: 1.05rem; }
/* ---------- class grid ---------- */
.classgrid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: clamp(16px, 2.5vw, 26px);
}
.class-card {
position: relative;
padding: 28px 24px 24px;
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.02);
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.3s ease;
overflow: hidden;
}
.class-card::after {
content: "";
position: absolute; inset: 0;
border-radius: inherit;
background: radial-gradient(380px 160px at 50% -20%, rgba(232,193,90,0.16), transparent 70%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.class-card:hover, .class-card:focus-visible {
transform: translateY(-6px);
border-color: var(--line-2);
box-shadow: 0 22px 50px rgba(0,0,0,0.5), var(--glow);
}
.class-card:hover::after, .class-card:focus-visible::after { opacity: 1; }
.class-card__crest {
width: 58px; height: 58px;
display: grid; place-items: center;
margin-bottom: 16px;
font-size: 1.6rem;
color: var(--accent);
border: 1px solid var(--line-2);
border-radius: 50%;
background: radial-gradient(circle, rgba(232,193,90,0.14), rgba(26,19,12,0.2));
transition: box-shadow 0.3s ease, transform 0.3s ease, color 0.3s ease;
}
.class-card:hover .class-card__crest,
.class-card:focus-visible .class-card__crest {
box-shadow: var(--glow);
transform: rotate(-6deg) scale(1.06);
color: #fff2cf;
}
.class-card__name { margin: 0; font-size: 1.4rem; font-weight: 700; letter-spacing: 0.03em; }
.class-card__race {
margin: 2px 0 12px;
font-family: "Cinzel", serif;
font-size: 0.72rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--accent-2);
}
.class-card__desc { margin: 0 0 18px; color: var(--muted); font-size: 0.98rem; }
.stats { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.stats li {
display: grid;
grid-template-columns: 64px 1fr;
align-items: center;
gap: 10px;
}
.stats li span {
font-family: "Cinzel", serif;
font-size: 0.62rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.stats .bar {
position: relative;
display: block;
height: 7px;
border-radius: 99px;
background: rgba(255,255,255,0.06);
overflow: hidden;
}
.stats .bar::before {
content: "";
position: absolute; inset: 0;
width: 0;
border-radius: inherit;
background: linear-gradient(90deg, var(--accent-deep), var(--accent), #fff2cf);
box-shadow: 0 0 10px rgba(232,193,90,0.5);
transition: width 1.1s cubic-bezier(.2,.7,.2,1);
}
.class-card.is-in .stats .bar::before { width: var(--v); }
/* ---------- lore scrolls ---------- */
.scrolls { max-width: 820px; margin: 0 auto; display: grid; gap: 16px; }
.scroll {
border: 1px solid var(--line);
border-radius: var(--r-md);
background:
linear-gradient(180deg, rgba(46,33,20,0.7), rgba(26,19,12,0.85));
overflow: hidden;
transition: border-color 0.25s ease, box-shadow 0.3s ease;
}
.scroll[open] { border-color: var(--line-2); box-shadow: 0 14px 40px rgba(0,0,0,0.4); }
.scroll__seal {
display: flex;
align-items: center;
gap: 16px;
padding: 18px 22px;
cursor: pointer;
list-style: none;
user-select: none;
}
.scroll__seal::-webkit-details-marker { display: none; }
.scroll__num {
flex: none;
width: 42px; height: 42px;
display: grid; place-items: center;
font-family: "Cinzel", serif;
font-weight: 900;
color: var(--accent);
border: 1px solid var(--line-2);
border-radius: 50%;
background: radial-gradient(circle, rgba(232,193,90,0.12), transparent 75%);
}
.scroll__title {
flex: 1;
font-family: "Cinzel", serif;
font-weight: 700;
font-size: 1.08rem;
letter-spacing: 0.02em;
}
.scroll__chev { color: var(--accent); transition: transform 0.3s ease; }
.scroll[open] .scroll__chev { transform: rotate(180deg); }
.scroll__body {
padding: 0 22px 22px 80px;
color: var(--parchment);
}
.scroll[open] .scroll__body { animation: unfurl 0.5s ease both; }
@keyframes unfurl {
from { opacity: 0; transform: translateY(-8px); clip-path: inset(0 0 100% 0); }
to { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }
}
/* ---------- factions ---------- */
.factions {
max-width: 1180px;
margin: 0 auto;
padding: clamp(40px, 7vw, 90px) clamp(16px, 5vw, 48px);
}
.faction-band {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
border: 1px solid var(--line);
border-radius: var(--r-lg);
overflow: hidden;
background: linear-gradient(180deg, rgba(46,33,20,0.5), rgba(18,13,7,0.7));
}
.faction {
padding: 30px 26px;
border-right: 1px solid var(--line);
transition: background 0.25s ease;
}
.faction:last-child { border-right: 0; }
.faction:hover { background: rgba(232,193,90,0.05); }
.faction__sigil {
font-size: 1.5rem;
color: var(--accent);
text-shadow: var(--glow);
}
.faction h3 { margin: 12px 0 6px; font-size: 1.2rem; font-weight: 700; }
.faction p { margin: 0; color: var(--muted); font-size: 0.95rem; }
/* ---------- closer ---------- */
.closer {
max-width: 880px;
margin: 0 auto clamp(48px, 8vw, 96px);
padding: clamp(40px, 6vw, 64px) clamp(22px, 5vw, 48px);
text-align: center;
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
background:
radial-gradient(700px 280px at 50% 0%, rgba(155,27,48,0.18), transparent 70%),
linear-gradient(180deg, rgba(46,33,20,0.7), rgba(18,13,7,0.85));
box-shadow: 0 26px 70px rgba(0,0,0,0.5);
}
.closer__title { margin: 0; font-size: clamp(1.5rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: 0.02em; }
.closer__sub { margin: 12px 0 26px; color: var(--muted); }
/* ---------- footer ---------- */
.footer { margin-top: 20px; }
.footer__rule {
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
opacity: 0.5;
}
.footer__inner {
max-width: 1180px;
margin: 0 auto;
padding: 30px clamp(16px, 5vw, 48px) 48px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 18px;
}
.footer__nav { display: flex; flex-wrap: wrap; gap: 8px 22px; }
.footer__nav a {
text-decoration: none;
color: var(--muted);
font-family: "Cinzel", serif;
font-size: 0.8rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.footer__nav a:hover { color: var(--accent); }
.footer__fine { flex-basis: 100%; margin: 6px 0 0; color: rgba(183,165,137,0.7); font-size: 0.85rem; }
/* ---------- scroll-reveal + shimmer ---------- */
.reveal { opacity: 0; transform: translateY(22px); }
.reveal.is-in {
opacity: 1;
transform: none;
transition: opacity 0.7s ease, transform 0.7s cubic-bezier(.2,.7,.2,1);
}
.reveal.is-in.shimmer { animation: goldshimmer 1.1s ease 0.1s both; }
@keyframes goldshimmer {
0% { box-shadow: 0 0 0 0 rgba(232,193,90,0); }
40% { box-shadow: 0 0 36px 2px rgba(232,193,90,0.35); }
100% { box-shadow: 0 0 0 0 rgba(232,193,90,0); }
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translate(-50%, 140%);
z-index: 60;
padding: 14px 24px;
max-width: min(90vw, 420px);
font-family: "Cinzel", serif;
font-size: 0.9rem;
letter-spacing: 0.04em;
color: #1a130c;
background: linear-gradient(180deg, #f4d780, var(--accent) 55%, var(--accent-deep));
border: 1px solid rgba(255,255,255,0.4);
border-radius: var(--r-sm);
box-shadow: 0 18px 50px rgba(0,0,0,0.5), var(--glow);
opacity: 0;
transition: transform 0.45s cubic-bezier(.2,.8,.2,1), opacity 0.3s ease;
pointer-events: none;
}
.toast.is-show { transform: translate(-50%, 0); opacity: 1; }
/* ---------- responsive ---------- */
@media (max-width: 760px) {
.nav > a { display: none; }
.faction { border-right: 0; border-bottom: 1px solid var(--line); }
.faction:last-child { border-bottom: 0; }
}
@media (max-width: 520px) {
body { font-size: 17px; }
.topbar { padding: 12px 16px; }
.brand__sep, .brand__game { display: none; }
.vista__sun { width: 160px; height: 160px; }
.vista__castle { width: 160px; transform: translateX(-50%) scale(0.85); }
.hero__frame { padding: 30px 20px; }
.hero__frame::before, .hero__frame::after { width: 30px; height: 30px; }
.hero__cta .btn { flex: 1 1 100%; justify-content: center; }
.scroll__body { padding-left: 22px; }
.scroll__seal { gap: 12px; padding: 16px; }
.toast { bottom: 16px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}
.reveal { opacity: 1; transform: none; }
.class-card.is-in .stats .bar::before { transition: none; }
}(function () {
"use strict";
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
/* ---------- toast helper ---------- */
var toastEl = document.querySelector(".toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
}, 2600);
}
/* ---------- scroll reveal with gold shimmer ---------- */
var reveals = Array.prototype.slice.call(document.querySelectorAll(".reveal"));
if (reduceMotion || !("IntersectionObserver" in window)) {
reveals.forEach(function (el) {
el.classList.add("is-in");
});
} else {
var io = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
el.classList.add("is-in");
if (el.classList.contains("class-card") || el.classList.contains("faction")) {
el.classList.add("shimmer");
}
io.unobserve(el);
});
},
{ threshold: 0.18, rootMargin: "0px 0px -8% 0px" }
);
reveals.forEach(function (el) {
io.observe(el);
});
}
/* ---------- wishlist toggle (synced across all buttons) ---------- */
var wishlistBtns = Array.prototype.slice.call(document.querySelectorAll("[data-wishlist]"));
var wishlisted = false;
function paintWishlist() {
wishlistBtns.forEach(function (btn) {
btn.setAttribute("aria-pressed", String(wishlisted));
var label = btn.querySelector(".wl-label");
if (label) {
label.textContent = wishlisted ? "Wishlisted" : (btn.classList.contains("btn--sm") ? "Wishlist" : "Add to Wishlist");
}
});
}
wishlistBtns.forEach(function (btn) {
btn.addEventListener("click", function () {
wishlisted = !wishlisted;
paintWishlist();
toast(
wishlisted
? "Sworn to the Vanguard — added to your wishlist."
: "Oath withdrawn — removed from your wishlist."
);
});
});
paintWishlist();
/* ---------- lore scroll accordion (single-open behaviour) ---------- */
var accordion = document.querySelector("[data-accordion]");
if (accordion) {
var scrolls = Array.prototype.slice.call(accordion.querySelectorAll(".scroll"));
scrolls.forEach(function (detail) {
detail.addEventListener("toggle", function () {
if (!detail.open) return;
scrolls.forEach(function (other) {
if (other !== detail && other.open) other.open = false;
});
});
});
}
/* ---------- class card keyboard activation (focus crest glow already CSS) ---------- */
var classCards = Array.prototype.slice.call(document.querySelectorAll(".class-card"));
classCards.forEach(function (card) {
card.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
var name = card.querySelector(".class-card__name");
toast("You consider the path of the " + (name ? name.textContent : "oathkeeper") + ".");
}
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ashen Vanguard — A Fantasy RPG by Nullforge</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700;900&family=EB+Garamond:ital,wght@0,400;0,500;0,600;1,400&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="topbar">
<div class="brand">
<span class="crest" aria-hidden="true">✦</span>
<span class="brand__name">Nullforge</span>
<span class="brand__sep" aria-hidden="true">·</span>
<span class="brand__game">Ashen Vanguard</span>
</div>
<nav class="nav" aria-label="Primary">
<a href="#classes">Classes</a>
<a href="#world">World</a>
<a href="#factions">Factions</a>
<button class="btn btn--ghost btn--sm" data-wishlist aria-pressed="false">
<span class="wl-icon" aria-hidden="true">♥</span>
<span class="wl-label">Wishlist</span>
</button>
</nav>
</header>
<main id="main">
<!-- HERO -->
<section class="hero" aria-labelledby="hero-title">
<div class="vista" aria-hidden="true">
<div class="vista__sky"></div>
<div class="vista__sun"></div>
<div class="vista__mountains m3"></div>
<div class="vista__mountains m2"></div>
<div class="vista__castle">
<span class="tower t1"></span>
<span class="tower t2"></span>
<span class="keep"></span>
<span class="tower t3"></span>
</div>
<div class="vista__mountains m1"></div>
<div class="vista__mist"></div>
</div>
<div class="hero__frame reveal">
<p class="eyebrow">An Age of Embers Begins</p>
<h1 id="hero-title" class="hero__title">Ashen Vanguard</h1>
<p class="hero__lore">
The old kingdoms have crumbled to cinder and the Ember Throne sits empty.
Gather a fellowship, claim a forgotten oath, and carry the last light through
the Hollow Reign before the long dark devours the realm.
</p>
<div class="hero__cta">
<a class="btn btn--gold" href="#world">
<span class="rune" aria-hidden="true">⚔</span> Begin Your Journey
</a>
<button class="btn btn--outline" data-wishlist aria-pressed="false">
<span class="wl-icon" aria-hidden="true">♥</span>
<span class="wl-label">Add to Wishlist</span>
</button>
</div>
<ul class="hero__meta" aria-label="Game details">
<li><strong>Chapter I</strong> The Last Ember</li>
<li><strong>Solo & Co-op</strong> up to 4</li>
<li><strong>Coming</strong> Winter of Ash</li>
</ul>
</div>
</section>
<!-- CLASSES / RACES -->
<section id="classes" class="section" aria-labelledby="classes-title">
<div class="section__head reveal">
<p class="eyebrow">Choose Your Path</p>
<h2 id="classes-title" class="section__title">Classes & Bloodlines</h2>
<p class="section__sub">Six oaths to swear, each with its own crest, virtues, and ruin.</p>
</div>
<div class="classgrid">
<article class="class-card reveal" tabindex="0">
<div class="class-card__crest" aria-hidden="true">⚔</div>
<h3 class="class-card__name">Emberblade</h3>
<p class="class-card__race">Sunsworn Human</p>
<p class="class-card__desc">A frontline oathkeeper whose blade drinks the last warmth of dying fires.</p>
<ul class="stats" aria-label="Stats">
<li><span>Might</span><i class="bar" style="--v:90%"></i></li>
<li><span>Valor</span><i class="bar" style="--v:80%"></i></li>
<li><span>Arcana</span><i class="bar" style="--v:25%"></i></li>
</ul>
</article>
<article class="class-card reveal" tabindex="0">
<div class="class-card__crest" aria-hidden="true">✶</div>
<h3 class="class-card__name">Ashweaver</h3>
<p class="class-card__race">Veiled Elf</p>
<p class="class-card__desc">Calls cinder-storms and binding hexes from the cold remnants of fallen stars.</p>
<ul class="stats" aria-label="Stats">
<li><span>Might</span><i class="bar" style="--v:30%"></i></li>
<li><span>Valor</span><i class="bar" style="--v:55%"></i></li>
<li><span>Arcana</span><i class="bar" style="--v:95%"></i></li>
</ul>
</article>
<article class="class-card reveal" tabindex="0">
<div class="class-card__crest" aria-hidden="true">☾</div>
<h3 class="class-card__name">Hollowstalker</h3>
<p class="class-card__race">Greywild Beastkin</p>
<p class="class-card__desc">Moves unseen through the Hollow Reign, marking quarry with moon-touched arrows.</p>
<ul class="stats" aria-label="Stats">
<li><span>Might</span><i class="bar" style="--v:60%"></i></li>
<li><span>Valor</span><i class="bar" style="--v:70%"></i></li>
<li><span>Arcana</span><i class="bar" style="--v:50%"></i></li>
</ul>
</article>
<article class="class-card reveal" tabindex="0">
<div class="class-card__crest" aria-hidden="true">✝</div>
<h3 class="class-card__name">Lightwarden</h3>
<p class="class-card__race">Orderborn Human</p>
<p class="class-card__desc">Tends the last flame, mending the fellowship and turning back the encroaching dark.</p>
<ul class="stats" aria-label="Stats">
<li><span>Might</span><i class="bar" style="--v:45%"></i></li>
<li><span>Valor</span><i class="bar" style="--v:90%"></i></li>
<li><span>Arcana</span><i class="bar" style="--v:75%"></i></li>
</ul>
</article>
<article class="class-card reveal" tabindex="0">
<div class="class-card__crest" aria-hidden="true">⛏</div>
<h3 class="class-card__name">Forgewright</h3>
<p class="class-card__race">Deepvein Dwarf</p>
<p class="class-card__desc">Wields runed warhammers forged in the under-halls of Nullforge Deep.</p>
<ul class="stats" aria-label="Stats">
<li><span>Might</span><i class="bar" style="--v:95%"></i></li>
<li><span>Valor</span><i class="bar" style="--v:65%"></i></li>
<li><span>Arcana</span><i class="bar" style="--v:40%"></i></li>
</ul>
</article>
<article class="class-card reveal" tabindex="0">
<div class="class-card__crest" aria-hidden="true">☠</div>
<h3 class="class-card__name">Cindercaller</h3>
<p class="class-card__race">Ashen Revenant</p>
<p class="class-card__desc">A pact-bound soul who barters with the dead to borrow forbidden ruin.</p>
<ul class="stats" aria-label="Stats">
<li><span>Might</span><i class="bar" style="--v:50%"></i></li>
<li><span>Valor</span><i class="bar" style="--v:35%"></i></li>
<li><span>Arcana</span><i class="bar" style="--v:88%"></i></li>
</ul>
</article>
</div>
</section>
<!-- WORLD LORE -->
<section id="world" class="section section--lore" aria-labelledby="world-title">
<div class="section__head reveal">
<p class="eyebrow">Chronicles of the Realm</p>
<h2 id="world-title" class="section__title">The Lore of Ardennfell</h2>
<p class="section__sub">Unfurl each scroll to read the chapters of a dying age.</p>
</div>
<div class="scrolls" data-accordion>
<details class="scroll reveal" open>
<summary class="scroll__seal">
<span class="scroll__num">I</span>
<span class="scroll__title">The Sundering of the Ember Throne</span>
<span class="scroll__chev" aria-hidden="true">▾</span>
</summary>
<div class="scroll__body">
<p>When the High King fell to his own crown, the Ember that warmed all Ardennfell guttered low.
Nine houses tore the realm asunder, each claiming the right to relight the throne — and each, in
turn, was swallowed by the cold they could not master.</p>
</div>
</details>
<details class="scroll reveal">
<summary class="scroll__seal">
<span class="scroll__num">II</span>
<span class="scroll__title">Rise of the Hollow Reign</span>
<span class="scroll__chev" aria-hidden="true">▾</span>
</summary>
<div class="scroll__body">
<p>From the unlit places came the Hollow — wraiths of every soul left to freeze in the dark. They
do not hunger for flesh, but for warmth, and where they pass the very memory of fire is unmade.</p>
</div>
</details>
<details class="scroll reveal">
<summary class="scroll__seal">
<span class="scroll__num">III</span>
<span class="scroll__title">The Oath of the Vanguard</span>
<span class="scroll__chev" aria-hidden="true">▾</span>
</summary>
<div class="scroll__body">
<p>A handful of wanderers swore the old vow at the Ashen Cairn: to carry the last true flame
across the ruined leagues to the Ember Throne, though every league should cost a life. They are
the Vanguard, and you are sworn among them.</p>
</div>
</details>
</div>
</section>
<!-- FACTIONS BAND -->
<section id="factions" class="factions" aria-labelledby="factions-title">
<div class="section__head reveal">
<p class="eyebrow">Sworn & Forsworn</p>
<h2 id="factions-title" class="section__title">The Factions of Ardennfell</h2>
</div>
<div class="faction-band">
<div class="faction reveal">
<span class="faction__sigil" aria-hidden="true">✦</span>
<h3>The Sunsworn Order</h3>
<p>Keepers of the last light, bound by oath to relight the throne.</p>
</div>
<div class="faction reveal">
<span class="faction__sigil" aria-hidden="true">☾</span>
<h3>Greywild Clans</h3>
<p>Beastkin of the deep forest who answer to no crown but the moon.</p>
</div>
<div class="faction reveal">
<span class="faction__sigil" aria-hidden="true">☠</span>
<h3>The Ashen Court</h3>
<p>Revenants who barter in souls and rule the cold places of the dead.</p>
</div>
<div class="faction reveal">
<span class="faction__sigil" aria-hidden="true">⚒</span>
<h3>Deepvein Holds</h3>
<p>Dwarven smiths of Nullforge Deep, forging the realm's last true steel.</p>
</div>
</div>
</section>
<!-- CTA STRIP -->
<section class="closer reveal" aria-label="Pre-order">
<h2 class="closer__title">The Ember waits for no oathkeeper.</h2>
<p class="closer__sub">Wishlist now and receive the Ashen Cairn banner on launch day.</p>
<div class="hero__cta">
<a class="btn btn--gold" href="#hero-title"><span class="rune" aria-hidden="true">⚔</span> Begin Your Journey</a>
<button class="btn btn--outline" data-wishlist aria-pressed="false">
<span class="wl-icon" aria-hidden="true">♥</span>
<span class="wl-label">Add to Wishlist</span>
</button>
</div>
</section>
</main>
<footer class="footer">
<div class="footer__rule" aria-hidden="true"></div>
<div class="footer__inner">
<div class="brand">
<span class="crest" aria-hidden="true">✦</span>
<span class="brand__name">Nullforge Studios</span>
</div>
<nav class="footer__nav" aria-label="Footer">
<a href="#classes">Classes</a>
<a href="#world">World</a>
<a href="#factions">Factions</a>
<a href="#main">Press Kit</a>
</nav>
<p class="footer__fine">© Ardennfell Age of Ash — a fictional title by Nullforge. All crests are heraldry of a realm that never was.</p>
</div>
</footer>
<div class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Fantasy RPG Landing
A regal, lore-rich landing page for the fictional fantasy RPG Ashen Vanguard by studio Nullforge, art-directed like an illuminated manuscript. The hero pairs a Cinzel display title and an ember-toned lore tagline inside a parchment-and-gold frame with ornate filigree corners, set against an entirely CSS-drawn vista — layered clip-path mountains, a glowing sun, and a ruined castle keep silhouetted under a dying-light sky. Two large call-to-action buttons, Begin Your Journey and a gold Add to Wishlist, anchor the page.
Below the fold, a Classes & Bloodlines grid presents six oaths — Emberblade, Ashweaver, Hollowstalker, Lightwarden, Forgewright, and Cindercaller — each card carrying a heraldic crest, a race line, and three stat bars that fill on reveal. A Lore of Ardennfell section uses an unfurling scroll accordion (single-open, keyboard-friendly) to reveal chapters of a crumbling age, followed by a four-house factions band and an ornate gold-rule footer.
Interactions are pure vanilla JavaScript: an IntersectionObserver drives the scroll-reveal with a gentle gold-shimmer on cards and factions, class crests glow on hover and focus, the wishlist state syncs across every button with toast feedback, and the lore scrolls behave as a tidy single-open accordion. Motion respects prefers-reduced-motion, the layout holds down to ~360px, and focus-visible rings keep it keyboard-usable throughout.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.