Game — Devlog / Update Post Layout
A dark neon devlog and update-post layout for a fictional action game: version badge, author meta and read time, CSS-art hero block, rich body sections with captioned in-game screenshots, an animated stat-bar panel, a glowing patch-highlights callout, an embedded changelog with copy button, sticky scroll-spy table of contents with read progress, prev and next post cards, emoji reactions with live counts, and a back-to-top button — all in Orbitron and Inter with vanilla JS.
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;
--font-display: "Orbitron", sans-serif;
--font-body: "Inter", system-ui, sans-serif;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-padding-top: 84px; }
body {
margin: 0;
background:
radial-gradient(1100px 500px at 80% -10%, rgba(124, 77, 255, 0.12), transparent 60%),
radial-gradient(900px 420px at 10% 0%, rgba(0, 229, 255, 0.08), transparent 55%),
var(--bg);
color: var(--text);
font-family: var(--font-body);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ============ Topbar ============ */
.topbar {
position: sticky;
top: 0;
z-index: 40;
background: rgba(10, 11, 16, 0.82);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 1180px;
margin: 0 auto;
padding: 12px 24px;
display: flex;
align-items: center;
gap: 24px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--font-display);
font-weight: 700;
font-size: 0.85rem;
letter-spacing: 0.14em;
color: var(--text);
}
.brand:hover { text-decoration: none; }
.brand-mark { color: var(--accent); text-shadow: var(--glow); font-size: 1rem; }
.brand-divider { color: var(--muted); opacity: 0.6; }
.brand-game { color: var(--muted); font-weight: 500; letter-spacing: 0.1em; font-size: 0.72rem; }
.topnav {
display: flex;
gap: 4px;
margin-left: auto;
}
.topnav-link {
font-size: 0.82rem;
font-weight: 600;
color: var(--muted);
padding: 7px 12px;
border-radius: var(--r-sm);
border: 1px solid transparent;
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
}
.topnav-link:hover { color: var(--text); background: var(--panel); text-decoration: none; }
.topnav-link.is-active {
color: var(--accent);
border-color: rgba(0, 229, 255, 0.35);
background: rgba(0, 229, 255, 0.08);
}
/* ============ Buttons ============ */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--font-display);
font-weight: 700;
letter-spacing: 0.08em;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
background: var(--panel);
color: var(--text);
cursor: pointer;
padding: 10px 18px;
font-size: 0.8rem;
transition: transform 0.15s ease, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease;
clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
}
.btn:hover { border-color: var(--accent); box-shadow: var(--glow); }
.btn:active { transform: translateY(1px) scale(0.98); }
.btn-accent {
background: linear-gradient(120deg, rgba(0, 229, 255, 0.18), rgba(124, 77, 255, 0.18));
border-color: rgba(0, 229, 255, 0.5);
color: var(--accent);
}
.btn-accent:hover { color: #fff; background: linear-gradient(120deg, rgba(0, 229, 255, 0.32), rgba(124, 77, 255, 0.32)); }
.btn-ghost { background: transparent; color: var(--muted); }
.btn-ghost:hover { color: var(--accent); }
.btn-sm { padding: 7px 14px; font-size: 0.7rem; }
/* ============ Page shell ============ */
.page {
max-width: 1180px;
margin: 0 auto;
padding: 36px 24px 80px;
}
/* ============ Post header ============ */
.crumbs {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
font-size: 0.78rem;
color: var(--muted);
margin-bottom: 18px;
}
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--accent); }
.crumbs [aria-current] { color: var(--text); font-weight: 600; }
.post-badges {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-bottom: 16px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: var(--font-display);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.12em;
padding: 5px 12px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
color: var(--muted);
clip-path: polygon(6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);
}
.badge-version {
color: #04141a;
background: linear-gradient(120deg, var(--accent), #36e2c8);
border-color: transparent;
box-shadow: var(--glow);
}
.badge-live { color: var(--success); border-color: rgba(54, 226, 122, 0.4); background: rgba(54, 226, 122, 0.08); }
.live-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--success);
box-shadow: 0 0 8px rgba(54, 226, 122, 0.8);
animation: pulse 1.8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.45; transform: scale(0.8); }
}
.post-title {
font-family: var(--font-display);
font-weight: 900;
font-size: clamp(1.5rem, 3.4vw, 2.5rem);
line-height: 1.18;
margin: 0 0 20px;
max-width: 900px;
background: linear-gradient(100deg, #fff 30%, var(--accent) 85%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.post-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px;
font-size: 0.84rem;
color: var(--muted);
margin-bottom: 28px;
}
.author { display: flex; align-items: center; gap: 10px; }
.avatar {
width: 40px;
height: 40px;
display: grid;
place-items: center;
border-radius: var(--r-sm);
font-family: var(--font-display);
font-weight: 700;
font-size: 0.78rem;
color: #04141a;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
}
.author-text { display: flex; flex-direction: column; line-height: 1.3; }
.author-name { color: var(--text); font-weight: 700; font-size: 0.88rem; }
.author-role { font-size: 0.72rem; }
.meta-sep { width: 4px; height: 4px; border-radius: 50%; background: var(--line-2); }
/* ============ Hero ============ */
.hero { margin: 0 0 36px; }
.hero-art {
position: relative;
height: clamp(220px, 36vw, 380px);
border-radius: var(--r-lg);
overflow: hidden;
border: 1px solid var(--line-2);
background:
radial-gradient(700px 300px at 70% 20%, rgba(124, 77, 255, 0.35), transparent 60%),
radial-gradient(600px 320px at 20% 90%, rgba(0, 229, 255, 0.25), transparent 60%),
linear-gradient(180deg, #11142a 0%, #0a0b14 100%);
box-shadow: inset 0 0 60px rgba(0, 0, 0, 0.55), 0 12px 40px rgba(0, 0, 0, 0.45);
}
.hero-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(0, 229, 255, 0.07) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 229, 255, 0.07) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: linear-gradient(180deg, transparent 0%, #000 45%, transparent 100%);
}
.hero-citadel {
position: absolute;
left: 50%;
bottom: -6%;
transform: translateX(-50%);
width: min(420px, 66%);
height: 72%;
background: linear-gradient(180deg, #1c2040, #0c0e1c);
clip-path: polygon(50% 0%, 60% 18%, 72% 14%, 78% 38%, 100% 52%, 92% 100%, 8% 100%, 0% 52%, 22% 38%, 28% 14%, 40% 18%);
border-top: 1px solid rgba(0, 229, 255, 0.35);
filter: drop-shadow(0 0 24px rgba(0, 229, 255, 0.22));
}
.hero-flare {
position: absolute;
left: 50%;
top: 8%;
width: 2px;
height: 42%;
background: linear-gradient(180deg, var(--accent), transparent);
box-shadow: 0 0 22px rgba(0, 229, 255, 0.9);
animation: flare 3.2s ease-in-out infinite;
}
@keyframes flare {
0%, 100% { opacity: 0.9; }
50% { opacity: 0.3; }
}
.hero-hud {
position: absolute;
top: 16px;
left: 16px;
right: 16px;
display: flex;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.hud-chip {
font-family: var(--font-display);
font-size: 0.62rem;
font-weight: 700;
letter-spacing: 0.16em;
color: var(--accent);
background: rgba(10, 11, 16, 0.72);
border: 1px solid rgba(0, 229, 255, 0.35);
padding: 6px 10px;
clip-path: polygon(6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);
}
.hud-chip-alt { color: var(--accent-2); border-color: rgba(124, 77, 255, 0.4); }
.hero-caption,
.shot figcaption,
.figure-note {
font-size: 0.78rem;
color: var(--muted);
margin-top: 10px;
}
/* ============ Layout ============ */
.layout {
display: grid;
grid-template-columns: 240px minmax(0, 1fr);
gap: 40px;
align-items: start;
}
/* ============ TOC ============ */
.toc-wrap { position: sticky; top: 84px; }
.toc {
background: linear-gradient(180deg, var(--panel), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px 16px;
clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
}
.toc-title {
font-family: var(--font-display);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
margin: 0 0 12px;
}
.toc-list {
list-style: none;
margin: 0 0 16px;
padding: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.toc-link {
display: block;
font-size: 0.82rem;
font-weight: 500;
color: var(--muted);
padding: 7px 10px;
border-left: 2px solid transparent;
border-radius: 0 var(--r-sm) var(--r-sm) 0;
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.toc-link:hover { color: var(--text); background: rgba(231, 233, 243, 0.05); text-decoration: none; }
.toc-link.is-active {
color: var(--accent);
border-left-color: var(--accent);
background: rgba(0, 229, 255, 0.08);
text-shadow: 0 0 12px rgba(0, 229, 255, 0.5);
}
.toc-progress {
height: 4px;
background: var(--panel-2);
border-radius: 999px;
overflow: hidden;
}
.toc-progress-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
box-shadow: var(--glow);
transition: width 0.15s linear;
}
.toc-progress-label {
margin: 8px 0 0;
font-family: var(--font-display);
font-size: 0.64rem;
letter-spacing: 0.14em;
color: var(--muted);
}
/* ============ Article body ============ */
.post-body { min-width: 0; }
.post-section { margin-bottom: 48px; }
.post-section h2 {
font-family: var(--font-display);
font-weight: 700;
font-size: 1.3rem;
letter-spacing: 0.04em;
margin: 0 0 16px;
padding-bottom: 10px;
border-bottom: 1px solid var(--line);
position: relative;
}
.post-section h2::after {
content: "";
position: absolute;
left: 0;
bottom: -1px;
width: 56px;
height: 2px;
background: linear-gradient(90deg, var(--accent), transparent);
box-shadow: var(--glow);
}
.post-section h3 {
font-family: var(--font-display);
font-weight: 700;
font-size: 0.98rem;
letter-spacing: 0.04em;
margin: 28px 0 12px;
color: var(--text);
}
.post-section p { margin: 0 0 14px; color: var(--text); font-size: 0.95rem; }
.post-section ul { margin: 0 0 14px; padding-left: 22px; }
.post-section li { margin-bottom: 8px; font-size: 0.95rem; }
.post-section strong { color: #fff; }
/* Stats row */
.stat-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-top: 22px;
}
.stat-card {
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 16px 12px;
text-align: center;
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
}
.stat-card:hover {
border-color: rgba(0, 229, 255, 0.45);
box-shadow: var(--glow);
transform: translateY(-2px);
}
.stat-num {
display: block;
font-family: var(--font-display);
font-weight: 900;
font-size: 1.5rem;
color: var(--accent);
text-shadow: 0 0 16px rgba(0, 229, 255, 0.45);
}
.stat-label {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
/* Screenshots */
.shot { margin: 22px 0; }
.shot-art {
position: relative;
height: clamp(160px, 28vw, 280px);
border-radius: var(--r-md);
border: 1px solid var(--line-2);
overflow: hidden;
box-shadow: inset 0 0 50px rgba(0, 0, 0, 0.5);
}
.shot-art-reach {
background:
radial-gradient(420px 200px at 30% 80%, rgba(255, 61, 113, 0.28), transparent 60%),
radial-gradient(500px 240px at 80% 10%, rgba(0, 229, 255, 0.3), transparent 60%),
linear-gradient(180deg, #181b33, #0b0c16);
}
.shot-art-raid {
background:
conic-gradient(from 200deg at 60% 40%, rgba(124, 77, 255, 0.35), transparent 30%, rgba(0, 229, 255, 0.25) 55%, transparent 75%),
linear-gradient(180deg, #141630, #0a0b14);
}
.shot-art::after {
content: "";
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(231, 233, 243, 0.045) 1px, transparent 1px),
linear-gradient(90deg, rgba(231, 233, 243, 0.045) 1px, transparent 1px);
background-size: 36px 36px;
}
.shot-hud {
position: absolute;
z-index: 1;
top: 12px;
left: 12px;
font-family: var(--font-display);
font-size: 0.6rem;
font-weight: 700;
letter-spacing: 0.16em;
color: var(--accent);
background: rgba(10, 11, 16, 0.72);
border: 1px solid rgba(0, 229, 255, 0.35);
padding: 5px 9px;
clip-path: polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px);
}
/* Stat bars */
.bars {
display: flex;
flex-direction: column;
gap: 12px;
margin: 22px 0 8px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
}
.bar-row { display: grid; grid-template-columns: 180px 1fr; align-items: center; gap: 14px; }
.bar-label { font-size: 0.78rem; font-weight: 600; color: var(--muted); }
.bar-track {
height: 18px;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
overflow: hidden;
}
.bar-fill {
height: 100%;
width: 0;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 8px;
background: linear-gradient(90deg, rgba(0, 229, 255, 0.25), var(--accent));
box-shadow: 0 0 14px rgba(0, 229, 255, 0.4);
transition: width 1s cubic-bezier(0.22, 1, 0.36, 1);
}
.bars.is-visible .bar-fill { width: var(--w); }
.bar-fill span {
font-family: var(--font-display);
font-size: 0.62rem;
font-weight: 700;
color: #04141a;
}
.bar-fill-alt { background: linear-gradient(90deg, rgba(124, 77, 255, 0.3), var(--accent-2)); box-shadow: 0 0 14px rgba(124, 77, 255, 0.4); }
.bar-fill-alt span { color: #fff; }
.bar-fill-warn { background: linear-gradient(90deg, rgba(255, 200, 87, 0.3), var(--warn)); box-shadow: 0 0 14px rgba(255, 200, 87, 0.35); }
.bar-fill-warn span { color: #1c1503; }
/* Callout */
.callout {
background:
linear-gradient(120deg, rgba(0, 229, 255, 0.08), rgba(124, 77, 255, 0.08)),
var(--panel);
border: 1px solid rgba(0, 229, 255, 0.35);
border-radius: var(--r-md);
padding: 22px;
box-shadow: inset 0 0 40px rgba(0, 229, 255, 0.05), 0 0 24px rgba(0, 229, 255, 0.08);
clip-path: polygon(16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%, 0 16px);
}
.callout-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.callout-icon {
display: grid;
place-items: center;
width: 34px;
height: 34px;
background: rgba(0, 229, 255, 0.12);
border: 1px solid rgba(0, 229, 255, 0.4);
border-radius: var(--r-sm);
font-size: 1rem;
box-shadow: var(--glow);
}
.callout-title {
font-family: var(--font-display);
font-size: 0.92rem;
font-weight: 700;
letter-spacing: 0.06em;
margin: 0;
}
.callout-list { list-style: none; margin: 0; padding: 0; }
.callout-list li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 9px 0;
border-bottom: 1px solid var(--line);
font-size: 0.9rem;
}
.callout-list li:last-child { border-bottom: 0; }
/* Chips */
.chip {
flex-shrink: 0;
font-family: var(--font-display);
font-size: 0.58rem;
font-weight: 700;
letter-spacing: 0.12em;
padding: 3px 8px;
margin-top: 2px;
border-radius: var(--r-sm);
border: 1px solid;
}
.chip-new { color: var(--success); border-color: rgba(54, 226, 122, 0.45); background: rgba(54, 226, 122, 0.1); }
.chip-changed { color: var(--warn); border-color: rgba(255, 200, 87, 0.45); background: rgba(255, 200, 87, 0.1); }
.chip-fixed { color: var(--accent); border-color: rgba(0, 229, 255, 0.45); background: rgba(0, 229, 255, 0.1); }
/* Changelog */
.changelog {
background: var(--bg-2);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
overflow: hidden;
}
.changelog-head {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: var(--panel);
border-bottom: 1px solid var(--line);
}
.changelog-version {
font-family: var(--font-display);
font-weight: 900;
font-size: 0.88rem;
color: var(--accent);
text-shadow: 0 0 12px rgba(0, 229, 255, 0.5);
}
.changelog-build { font-size: 0.72rem; color: var(--muted); margin-right: auto; }
.changelog-list { list-style: none; margin: 0; padding: 8px 0; }
.changelog-list li {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px 16px;
font-size: 0.86rem;
font-family: ui-monospace, "SF Mono", Menlo, monospace;
color: var(--text);
border-left: 2px solid transparent;
transition: background 0.15s ease, border-color 0.15s ease;
}
.changelog-list li:hover { background: rgba(0, 229, 255, 0.05); border-left-color: var(--accent); }
/* ============ Reactions ============ */
.reactions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
padding: 18px 0;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
margin: 8px 0 28px;
}
.reactions-label {
font-family: var(--font-display);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.react-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--panel);
border: 1px solid var(--line-2);
border-radius: 999px;
color: var(--muted);
font-family: var(--font-body);
font-weight: 700;
font-size: 0.82rem;
padding: 8px 14px;
cursor: pointer;
transition: transform 0.15s ease, border-color 0.2s ease, box-shadow 0.2s ease, color 0.2s ease, background 0.2s ease;
}
.react-btn:hover { border-color: var(--accent); color: var(--text); transform: translateY(-1px); }
.react-btn:active { transform: scale(0.94); }
.react-btn[aria-pressed="true"] {
border-color: var(--accent);
color: var(--accent);
background: rgba(0, 229, 255, 0.1);
box-shadow: var(--glow);
}
.react-btn.is-popping .react-emoji { animation: pop 0.35s ease; }
@keyframes pop {
0% { transform: scale(1); }
45% { transform: scale(1.5) rotate(-8deg); }
100% { transform: scale(1); }
}
.react-emoji { font-size: 1rem; line-height: 1; }
.comments-link { margin-left: auto; font-size: 0.84rem; font-weight: 600; }
/* ============ Prev / next ============ */
.post-nav {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.post-nav-card {
display: flex;
flex-direction: column;
gap: 8px;
background: linear-gradient(180deg, var(--panel), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
color: var(--text);
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
}
.post-nav-card:hover {
text-decoration: none;
border-color: rgba(0, 229, 255, 0.45);
box-shadow: var(--glow);
transform: translateY(-2px);
}
.post-nav-next { text-align: right; align-items: flex-end; }
.post-nav-dir {
font-family: var(--font-display);
font-size: 0.64rem;
font-weight: 700;
letter-spacing: 0.16em;
color: var(--muted);
}
.post-nav-badge {
font-family: var(--font-display);
font-size: 0.6rem;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--accent-2);
border: 1px solid rgba(124, 77, 255, 0.45);
background: rgba(124, 77, 255, 0.1);
padding: 3px 8px;
border-radius: var(--r-sm);
width: fit-content;
}
.post-nav-title { font-size: 0.9rem; font-weight: 600; line-height: 1.35; }
/* ============ Footer ============ */
.footer {
border-top: 1px solid var(--line);
padding: 24px;
text-align: center;
font-size: 0.74rem;
color: var(--muted);
}
/* ============ Back to top ============ */
.back-top {
position: fixed;
right: 22px;
bottom: 22px;
z-index: 50;
display: inline-flex;
align-items: center;
gap: 6px;
font-family: var(--font-display);
font-size: 0.66rem;
font-weight: 700;
letter-spacing: 0.14em;
color: var(--accent);
background: rgba(10, 11, 16, 0.88);
border: 1px solid rgba(0, 229, 255, 0.5);
border-radius: var(--r-sm);
padding: 10px 14px;
cursor: pointer;
box-shadow: var(--glow);
clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
animation: rise 0.3s ease;
transition: transform 0.15s ease, background 0.2s ease;
}
.back-top:hover { background: rgba(0, 229, 255, 0.14); transform: translateY(-2px); }
.back-top:active { transform: translateY(0) scale(0.96); }
@keyframes rise {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
/* ============ Toasts ============ */
.toast-host {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%);
z-index: 60;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
pointer-events: none;
}
.toast {
font-family: var(--font-display);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.1em;
color: var(--accent);
background: rgba(10, 11, 16, 0.92);
border: 1px solid rgba(0, 229, 255, 0.5);
box-shadow: var(--glow);
padding: 10px 18px;
clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
animation: toast-in 0.25s ease;
}
.toast.is-leaving { opacity: 0; transform: translateY(8px); transition: opacity 0.3s ease, transform 0.3s ease; }
@keyframes toast-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* ============ Reduced motion ============ */
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
/* ============ Responsive ============ */
@media (max-width: 920px) {
.layout { grid-template-columns: 1fr; gap: 24px; }
.toc-wrap { position: static; order: -1; }
.toc { clip-path: none; }
.toc-list { flex-direction: row; flex-wrap: wrap; gap: 6px; }
.toc-link { border-left: 0; border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px; font-size: 0.76rem; }
.toc-link.is-active { border-color: rgba(0, 229, 255, 0.5); }
.toc-progress, .toc-progress-label { display: none; }
.topnav { display: none; }
}
@media (max-width: 520px) {
.page { padding: 24px 16px 64px; }
.topbar-inner { padding: 10px 16px; gap: 12px; }
.brand-game { display: none; }
.post-title { font-size: 1.35rem; }
.post-meta { gap: 10px; font-size: 0.78rem; }
.stat-row { grid-template-columns: repeat(2, 1fr); }
.bar-row { grid-template-columns: 1fr; gap: 6px; }
.post-nav { grid-template-columns: 1fr; }
.post-nav-next { text-align: left; align-items: flex-start; }
.reactions { gap: 8px; }
.comments-link { margin-left: 0; width: 100%; }
.changelog-head { flex-wrap: wrap; }
.back-top { right: 14px; bottom: 14px; }
}/* Game devlog — scroll-spy TOC, smooth scroll, reactions, back-to-top.
Vanilla JS, no dependencies. Fictional demo data. */
(() => {
"use strict";
const $ = (sel, root = document) => root.querySelector(sel);
const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
/* ---------- Toast helper ---------- */
const toastHost = $("#toastHost");
function toast(msg) {
const el = document.createElement("div");
el.className = "toast";
el.textContent = msg;
toastHost.appendChild(el);
setTimeout(() => {
el.classList.add("is-leaving");
el.addEventListener("transitionend", () => el.remove(), { once: true });
setTimeout(() => el.remove(), 600); // reduced-motion fallback
}, 2200);
}
/* ---------- Scroll-spy TOC ---------- */
const tocLinks = $$(".toc-link");
const sections = tocLinks
.map((link) => $(link.getAttribute("href")))
.filter(Boolean);
function setActive(id) {
tocLinks.forEach((link) => {
const active = link.getAttribute("href") === `#${id}`;
link.classList.toggle("is-active", active);
if (active) link.setAttribute("aria-current", "true");
else link.removeAttribute("aria-current");
});
}
const spy = new IntersectionObserver(
(entries) => {
// Pick the entry closest to the top band that is intersecting.
const visible = entries
.filter((e) => e.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
if (visible.length) setActive(visible[0].target.id);
},
{ rootMargin: "-20% 0px -65% 0px", threshold: 0 }
);
sections.forEach((s) => spy.observe(s));
/* ---------- Smooth scroll for all in-page anchors ---------- */
$$('a[href^="#"]').forEach((a) => {
a.addEventListener("click", (e) => {
const id = a.getAttribute("href");
if (id.length < 2) return;
const target = $(id);
if (!target) return;
e.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
history.replaceState(null, "", id);
if (a.classList.contains("toc-link")) setActive(id.slice(1));
});
});
/* ---------- Read progress ---------- */
const post = $("#post");
const progressFill = $("#readProgress");
const pctLabel = $("#readPct");
function updateProgress() {
if (!post || !progressFill) return;
const rect = post.getBoundingClientRect();
const total = rect.height - window.innerHeight;
const read = Math.min(Math.max(-rect.top, 0), Math.max(total, 1));
const pct = total > 0 ? Math.round((read / total) * 100) : 0;
progressFill.style.width = `${pct}%`;
if (pctLabel) pctLabel.textContent = String(pct);
}
/* ---------- Back to top ---------- */
const backTop = $("#backTop");
function updateBackTop() {
backTop.hidden = window.scrollY < 480;
}
backTop.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
backTop.blur();
});
let ticking = false;
window.addEventListener(
"scroll",
() => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
updateProgress();
updateBackTop();
ticking = false;
});
},
{ passive: true }
);
updateProgress();
updateBackTop();
/* ---------- Stat bars: animate when visible ---------- */
const bars = $(".bars");
if (bars) {
const barWatcher = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
bars.classList.add("is-visible");
barWatcher.disconnect();
}
});
},
{ threshold: 0.4 }
);
barWatcher.observe(bars);
}
/* ---------- Reactions ---------- */
const reactionNames = {
fire: "Fire",
gg: "GG",
hype: "Hype",
heart: "Love",
};
$$(".react-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const pressed = btn.getAttribute("aria-pressed") === "true";
const next = !pressed;
btn.setAttribute("aria-pressed", String(next));
const countEl = $(".react-count", btn);
const base = Number(countEl.dataset.base);
countEl.textContent = String(base + (next ? 1 : 0));
if (next) {
btn.classList.remove("is-popping");
// restart the pop animation
void btn.offsetWidth;
btn.classList.add("is-popping");
toast(`${reactionNames[btn.dataset.reaction] || "Reaction"} added`);
} else {
toast("Reaction removed");
}
});
});
/* ---------- Misc demo actions ---------- */
const actions = {
wishlist: () => toast("Added to wishlist — see you in the Reach"),
comments: () => toast("Comments — demo only"),
"full-notes": () => toast("Full patch notes — demo only"),
"nav-prev": () => toast("Loading Update 1.3.2…"),
"nav-next": () => toast("Loading dev diary…"),
"copy-log": async () => {
const text = $$("#changelogList li")
.map((li) => `- ${li.textContent.replace(/\s+/g, " ").trim()}`)
.join("\n");
try {
await navigator.clipboard.writeText(`Ashen Vanguard v1.4.0\n${text}`);
toast("Changelog copied to clipboard");
} catch {
toast("Clipboard unavailable in this context");
}
},
};
$$("[data-action]").forEach((el) => {
el.addEventListener("click", (e) => {
const fn = actions[el.dataset.action];
if (!fn) return;
if (el.tagName === "A") e.preventDefault();
fn();
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Update 1.4 — Embers of the Hollow | Ashen Vanguard Devlog</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>
<header class="topbar">
<div class="topbar-inner">
<a class="brand" href="#" aria-label="Nullforge Studio home">
<span class="brand-mark" aria-hidden="true">◢</span>
<span class="brand-name">NULLFORGE</span>
<span class="brand-divider" aria-hidden="true">/</span>
<span class="brand-game">Ashen Vanguard</span>
</a>
<nav class="topnav" aria-label="Site">
<a href="#" class="topnav-link">News</a>
<a href="#" class="topnav-link is-active" aria-current="page">Devlogs</a>
<a href="#" class="topnav-link">Roadmap</a>
<a href="#" class="topnav-link">Media</a>
</nav>
<button class="btn btn-accent btn-sm" type="button" data-action="wishlist">+ Wishlist</button>
</div>
</header>
<main class="page">
<!-- ===== Post header ===== -->
<header class="post-head">
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#">Devlogs</a>
<span aria-hidden="true">›</span>
<a href="#">Patch Notes</a>
<span aria-hidden="true">›</span>
<span aria-current="page">Update 1.4</span>
</nav>
<div class="post-badges">
<span class="badge badge-version">UPDATE 1.4</span>
<span class="badge badge-tag">Patch Notes</span>
<span class="badge badge-tag">New Region</span>
<span class="badge badge-live"><span class="live-dot" aria-hidden="true"></span> Live on all platforms</span>
</div>
<h1 class="post-title">Embers of the Hollow — New Region, Sentinel Rework & Co-op Raids</h1>
<div class="post-meta">
<div class="author">
<span class="avatar" aria-hidden="true">MK</span>
<div class="author-text">
<span class="author-name">Mara Kessler</span>
<span class="author-role">Lead Systems Designer</span>
</div>
</div>
<span class="meta-sep" aria-hidden="true"></span>
<time datetime="2026-06-09">June 9, 2026</time>
<span class="meta-sep" aria-hidden="true"></span>
<span>11 min read</span>
<span class="meta-sep" aria-hidden="true"></span>
<span aria-label="142 comments">💬 142 comments</span>
</div>
</header>
<!-- ===== Hero ===== -->
<figure class="hero" role="img" aria-label="Concept art of the Hollow Reach region: a ruined citadel under a cyan storm">
<div class="hero-art">
<div class="hero-grid" aria-hidden="true"></div>
<div class="hero-citadel" aria-hidden="true"></div>
<div class="hero-flare" aria-hidden="true"></div>
<div class="hero-hud" aria-hidden="true">
<span class="hud-chip">REGION // HOLLOW REACH</span>
<span class="hud-chip hud-chip-alt">BUILD 1.4.0-7731</span>
</div>
</div>
<figcaption class="hero-caption">Key art — the Hollow Reach citadel, new endgame region in Update 1.4.</figcaption>
</figure>
<!-- ===== Body grid ===== -->
<div class="layout">
<!-- Sticky TOC -->
<aside class="toc-wrap" aria-label="Table of contents">
<nav class="toc" id="toc">
<p class="toc-title">On this page</p>
<ol class="toc-list">
<li><a href="#overview" class="toc-link">Overview</a></li>
<li><a href="#hollow-reach" class="toc-link">The Hollow Reach</a></li>
<li><a href="#sentinel-rework" class="toc-link">Sentinel class rework</a></li>
<li><a href="#highlights" class="toc-link">Patch highlights</a></li>
<li><a href="#changelog" class="toc-link">Changelog 1.4.0</a></li>
<li><a href="#whats-next" class="toc-link">What's next</a></li>
</ol>
<div class="toc-progress" aria-hidden="true">
<div class="toc-progress-fill" id="readProgress"></div>
</div>
<p class="toc-progress-label"><span id="readPct">0</span>% read</p>
</nav>
</aside>
<!-- Article -->
<article class="post-body" id="post">
<section id="overview" class="post-section">
<h2>Overview</h2>
<p>
Update 1.4 is the biggest content drop since launch. We're opening the <strong>Hollow Reach</strong>,
a storm-locked endgame region with three new biomes, rebuilding the Sentinel class from the frame up,
and shipping the first iteration of <strong>4-player co-op raids</strong>. The patch is live now on
PC, PlayStation, and Xbox — cross-progression carries everything over.
</p>
<p>
This devlog walks through the headline features, the reasoning behind the Sentinel changes, and the
full changelog. If you only have two minutes, jump to the
<a href="#highlights">patch highlights</a> below.
</p>
<div class="stat-row" role="list" aria-label="Update at a glance">
<div class="stat-card" role="listitem">
<span class="stat-num">3</span>
<span class="stat-label">New biomes</span>
</div>
<div class="stat-card" role="listitem">
<span class="stat-num">47</span>
<span class="stat-label">Weapons & mods</span>
</div>
<div class="stat-card" role="listitem">
<span class="stat-num">212</span>
<span class="stat-label">Bug fixes</span>
</div>
<div class="stat-card" role="listitem">
<span class="stat-num">9.2</span>
<span class="stat-label">GB download</span>
</div>
</div>
</section>
<section id="hollow-reach" class="post-section">
<h2>The Hollow Reach</h2>
<p>
The Reach sits beyond the Ember Gate, sealed since the prologue. It's our densest region yet:
verticality-first layout, dynamic storm cells that reroute patrol AI, and a region-wide reputation
track with the <em>Cindermark Covenant</em>. Recommended power: <strong>740+</strong>.
</p>
<figure class="shot">
<div class="shot-art shot-art-reach" role="img" aria-label="In-game screenshot of the Ember Gate opening into the Hollow Reach">
<span class="shot-hud" aria-hidden="true">CAM 02 · EMBER GATE</span>
</div>
<figcaption>The Ember Gate — your first view into the Reach. Storm cells visibly roll across the skybox in real time.</figcaption>
</figure>
<p>
Storms aren't cosmetic. When a cell passes over a zone, enemy factions retreat to shelter routes,
opening stealth paths that don't exist in clear weather — and Voltaic enemies gain a chain-arc buff,
so the risk scales with the reward.
</p>
<h3>Co-op raids: Vault of Static</h3>
<p>
The first raid, <strong>Vault of Static</strong>, supports four players with role-agnostic mechanics:
every encounter can be solved by any class composition, but coordination shortcuts exist for
premade squads. Expect a 45–70 minute clear on your first weeks.
</p>
<figure class="shot">
<div class="shot-art shot-art-raid" role="img" aria-label="Screenshot of four players facing the raid boss in the Vault of Static">
<span class="shot-hud" aria-hidden="true">RAID · PHASE 3/4</span>
</div>
<figcaption>Vault of Static, phase three — the Conduit Warden splits the arena into rotating live sectors.</figcaption>
</figure>
</section>
<section id="sentinel-rework" class="post-section">
<h2>Sentinel class rework</h2>
<p>
The Sentinel has been over-represented in defensive play and under-represented everywhere else —
<strong>61%</strong> of Sentinel builds used the same three barrier mods. The rework keeps the
class fantasy (the immovable anchor) but gives every barrier an offensive counterpart.
</p>
<ul>
<li><strong>Aegis Pulse</strong> now detonates when the barrier expires, scaling with damage absorbed.</li>
<li><strong>Bulwark Dash</strong> replaces Static Wall — a mobile shield-charge that staggers elites.</li>
<li>Barrier uptime reduced from 12s → 8s; cooldown reduced from 24s → 15s. More decisions, less camping.</li>
</ul>
<div class="bars" aria-label="Sentinel pick rate by mode, internal playtests">
<div class="bar-row">
<span class="bar-label">Story pick rate</span>
<div class="bar-track"><div class="bar-fill" style="--w:38%"><span>38%</span></div></div>
</div>
<div class="bar-row">
<span class="bar-label">Raid pick rate (playtest)</span>
<div class="bar-track"><div class="bar-fill bar-fill-alt" style="--w:27%"><span>27%</span></div></div>
</div>
<div class="bar-row">
<span class="bar-label">PvP pick rate (playtest)</span>
<div class="bar-track"><div class="bar-fill bar-fill-warn" style="--w:22%"><span>22%</span></div></div>
</div>
</div>
<p class="figure-note">Internal playtest data, build 1.4.0-rc3. We'll publish live numbers in two weeks.</p>
</section>
<section id="highlights" class="post-section">
<h2>Patch highlights</h2>
<aside class="callout" aria-label="Patch highlights summary">
<div class="callout-head">
<span class="callout-icon" aria-hidden="true">⚡</span>
<h3 class="callout-title">Update 1.4 at a glance</h3>
</div>
<ul class="callout-list">
<li><span class="chip chip-new">NEW</span> Hollow Reach region — 3 biomes, dynamic storm cells, Cindermark reputation track</li>
<li><span class="chip chip-new">NEW</span> Vault of Static — first 4-player co-op raid with weekly rotating modifiers</li>
<li><span class="chip chip-changed">REWORK</span> Sentinel class — offensive barrier kit, Bulwark Dash, retuned cooldowns</li>
<li><span class="chip chip-changed">UX</span> Loadout manager 2.0 — save 12 presets, share via build codes</li>
<li><span class="chip chip-fixed">FIX</span> 212 bug fixes including the infamous "phantom dodge" input drop</li>
</ul>
</aside>
</section>
<section id="changelog" class="post-section">
<h2>Changelog 1.4.0</h2>
<p>Abbreviated changelog below — the full 1,400-line version lives on the <a href="#" data-action="full-notes">patch notes portal</a>.</p>
<div class="changelog" role="region" aria-label="Changelog excerpt for version 1.4.0">
<div class="changelog-head">
<span class="changelog-version">v1.4.0</span>
<span class="changelog-build">build 7731 · 2026-06-09</span>
<button class="btn btn-ghost btn-sm" type="button" data-action="copy-log">Copy</button>
</div>
<ul class="changelog-list" id="changelogList">
<li><span class="chip chip-new">ADDED</span> Hollow Reach region with three biomes: Cinder Flats, the Undervault, Static Spires</li>
<li><span class="chip chip-new">ADDED</span> Raid: Vault of Static (4-player, weekly modifiers, power 760 recommended)</li>
<li><span class="chip chip-new">ADDED</span> 47 weapons and mods, including the Resonance frame archetype</li>
<li><span class="chip chip-changed">CHANGED</span> Sentinel: Aegis Pulse detonation, Bulwark Dash, barrier 12s→8s, CD 24s→15s</li>
<li><span class="chip chip-changed">CHANGED</span> Loadout manager: 12 preset slots, shareable build codes</li>
<li><span class="chip chip-changed">CHANGED</span> Storm cells now affect enemy AI routing and Voltaic damage</li>
<li><span class="chip chip-fixed">FIXED</span> "Phantom dodge" — dodge inputs dropped during weapon-swap animation frames</li>
<li><span class="chip chip-fixed">FIXED</span> Co-op revive prompt failing to appear near ledges</li>
<li><span class="chip chip-fixed">FIXED</span> HDR black crush in the Undervault on OLED displays</li>
</ul>
</div>
</section>
<section id="whats-next" class="post-section">
<h2>What's next</h2>
<p>
Update 1.5 (targeting September) brings ranked PvP seasons and the second raid wing. We'll share a
Sentinel data check-in within two weeks, and the next dev stream is <strong>June 20 on our channel</strong> —
bring your raid clears and your bug reports.
</p>
<p>— Mara & the Nullforge combat team</p>
</section>
<!-- Reactions -->
<div class="reactions" role="group" aria-label="React to this post">
<span class="reactions-label">React:</span>
<button class="react-btn" type="button" data-reaction="fire" aria-pressed="false">
<span class="react-emoji" aria-hidden="true">🔥</span><span class="react-count" data-base="318">318</span>
</button>
<button class="react-btn" type="button" data-reaction="gg" aria-pressed="false">
<span class="react-emoji" aria-hidden="true">🎮</span><span class="react-count" data-base="201">201</span>
</button>
<button class="react-btn" type="button" data-reaction="hype" aria-pressed="false">
<span class="react-emoji" aria-hidden="true">⚡</span><span class="react-count" data-base="164">164</span>
</button>
<button class="react-btn" type="button" data-reaction="heart" aria-pressed="false">
<span class="react-emoji" aria-hidden="true">💜</span><span class="react-count" data-base="97">97</span>
</button>
<a class="comments-link" href="#" data-action="comments">142 comments →</a>
</div>
<!-- Prev / next -->
<nav class="post-nav" aria-label="Adjacent devlogs">
<a href="#" class="post-nav-card post-nav-prev" data-action="nav-prev">
<span class="post-nav-dir">← Previous</span>
<span class="post-nav-badge">UPDATE 1.3.2</span>
<span class="post-nav-title">Hotfix: Voltaic damage stacking & matchmaking stability</span>
</a>
<a href="#" class="post-nav-card post-nav-next" data-action="nav-next">
<span class="post-nav-dir">Next →</span>
<span class="post-nav-badge">DEV DIARY</span>
<span class="post-nav-title">Designing the Vault of Static: raids without role locks</span>
</a>
</nav>
</article>
</div>
</main>
<footer class="footer">
<p>© 2086 Nullforge Interactive — Ashen Vanguard. Fictional studio & game for demo purposes.</p>
</footer>
<button class="back-top" id="backTop" type="button" aria-label="Back to top" hidden>
<span aria-hidden="true">▲</span> TOP
</button>
<div class="toast-host" id="toastHost" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Devlog / Update Post Layout
A full devlog / patch-notes post page for the fictional game Ashen Vanguard by studio Nullforge. The post header stacks a glowing “Update 1.4” version badge, gradient title, author card, date, read time, and comment count over a CSS-drawn hero key-art block with HUD chips. The body mixes prose sections, captioned screenshot figures, an animated Sentinel pick-rate bar panel, a neon patch-highlights callout with NEW / REWORK / FIX chips, and an embedded monospace changelog excerpt with a copy-to-clipboard button.
A sticky sidebar holds the table of contents with a read-progress bar; on narrow screens it collapses into pill links above the article. Below the post sit emoji reaction toggles with live counts, a comments link, and prev/next post cards with angled clip-path corners.
The script wires an IntersectionObserver scroll-spy that highlights the active TOC link,
smooth-scrolls every in-page anchor, animates the stat bars on first view, toggles reactions
with aria-pressed and a pop animation, tracks read percentage, and reveals a back-to-top
button after scrolling — every control fires real feedback through a small toast() helper.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.