Comics — Browse / Discover (genre grid)
A discover page for the fictional Inkwell comics platform, art-directed in bold ink-and-halftone comic style. A featured carousel banner cycles hero series with auto-play, manual arrows, dot tabs, and keyboard support, pausing on hover or focus. Multi-select genre chips filter a responsive grid of series cards — CSS-gradient cover art, halftone SFX lettering, title, author, genre tag, star rating, and NEW or HOT badges — while a sort dropdown reorders by Trending, New, or Top rated. Cards lift on hover and every action confirms through a toast.
MCP
Codice
:root {
--ink: #0e0e12;
--ink-2: #23232b;
--paper: #fdfcf7;
--panel: #ffffff;
--accent: #ff2e4d;
--accent-2: #ffd23f;
--accent-blue: #2e6bff;
--muted: #6b6b78;
--line: rgba(14, 14, 18, 0.14);
--line-2: rgba(14, 14, 18, 0.28);
--halftone: radial-gradient(circle, rgba(14, 14, 18, 0.18) 1px, transparent 1.6px);
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 6px 6px 0 var(--ink);
--shadow-sm: 3px 3px 0 var(--ink);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
color: var(--ink);
background-color: var(--paper);
background-image: var(--halftone);
background-size: 6px 6px;
}
.wrap {
width: min(1140px, 100% - 2.5rem);
margin-inline: auto;
}
.skip-link {
position: absolute;
left: -9999px;
top: 0;
background: var(--ink);
color: var(--paper);
padding: 0.6rem 1rem;
z-index: 50;
border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus {
left: 0;
}
/* ---------- buttons ---------- */
.btn {
font-family: inherit;
font-weight: 700;
font-size: 0.95rem;
border: 3px solid var(--ink);
border-radius: var(--r-sm);
padding: 0.6rem 1.1rem;
cursor: pointer;
background: var(--panel);
color: var(--ink);
box-shadow: var(--shadow-sm);
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.btn:hover {
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 var(--ink);
}
.btn:active {
transform: translate(2px, 2px);
box-shadow: 1px 1px 0 var(--ink);
}
.btn:focus-visible {
outline: 3px solid var(--accent-blue);
outline-offset: 2px;
}
.btn-accent {
background: var(--accent);
color: #fff;
}
.btn-ghost {
background: transparent;
box-shadow: none;
border-width: 2px;
}
.btn-ghost:hover {
background: var(--accent-2);
}
.btn-sm {
padding: 0.35rem 0.8rem;
font-size: 0.85rem;
}
/* ---------- topbar ---------- */
.topbar {
border-bottom: 3px solid var(--ink);
background: var(--panel);
position: sticky;
top: 0;
z-index: 20;
}
.topbar-inner {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 0.7rem 0;
}
.brand {
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: var(--ink);
}
.brand-mark {
font-family: "Bangers", cursive;
font-size: 1.6rem;
line-height: 1;
width: 2rem;
height: 2rem;
display: grid;
place-items: center;
background: var(--accent-2);
border: 3px solid var(--ink);
border-radius: var(--r-sm);
transform: rotate(-6deg);
box-shadow: var(--shadow-sm);
}
.brand-name {
font-family: "Bangers", cursive;
font-size: 1.7rem;
letter-spacing: 1px;
}
.topnav {
display: flex;
gap: 1.1rem;
margin-left: auto;
}
.topnav a {
text-decoration: none;
color: var(--ink-2);
font-weight: 600;
padding-bottom: 2px;
border-bottom: 3px solid transparent;
}
.topnav a:hover {
color: var(--accent);
}
.topnav a.is-active {
border-bottom-color: var(--accent);
}
/* ---------- carousel ---------- */
.carousel {
position: relative;
margin: 1.6rem 0 1.4rem;
}
.carousel-viewport {
overflow: hidden;
border: 3px solid var(--ink);
border-radius: var(--r-lg);
box-shadow: var(--shadow);
}
.carousel-track {
list-style: none;
margin: 0;
padding: 0;
display: flex;
transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.slide {
flex: 0 0 100%;
min-height: 320px;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
align-items: center;
}
.slide-copy {
padding: 2rem 2.4rem;
}
.slide-kicker {
display: inline-block;
font-weight: 800;
font-size: 0.75rem;
letter-spacing: 0.12em;
text-transform: uppercase;
background: var(--ink);
color: var(--accent-2);
padding: 0.25rem 0.6rem;
border-radius: var(--r-sm);
}
.slide-title {
font-family: "Bangers", cursive;
font-size: clamp(2.6rem, 6vw, 4rem);
line-height: 0.95;
margin: 0.7rem 0 0.4rem;
letter-spacing: 1px;
-webkit-text-stroke: 1px var(--ink);
}
.slide--a .slide-title { color: #fff; }
.slide--b .slide-title { color: var(--accent-2); }
.slide--c .slide-title { color: #fff; }
.slide-sub {
margin: 0 0 1rem;
max-width: 38ch;
font-weight: 500;
}
.slide--a .slide-sub,
.slide--b .slide-sub,
.slide--c .slide-sub { color: rgba(255, 255, 255, 0.9); }
.slide-meta {
display: flex;
align-items: center;
gap: 0.6rem;
flex-wrap: wrap;
margin-bottom: 1.2rem;
}
.slide-art {
align-self: stretch;
display: grid;
place-items: center;
position: relative;
background-image: var(--halftone);
background-size: 8px 8px;
border-left: 3px solid var(--ink);
}
.slide-art .sfx {
font-family: "Bangers", cursive;
font-size: clamp(2.4rem, 7vw, 4.4rem);
color: var(--accent-2);
-webkit-text-stroke: 2px var(--ink);
transform: rotate(-8deg);
text-shadow: 4px 4px 0 var(--ink);
}
.slide--a { background: linear-gradient(135deg, #1b2a4a, #2e6bff); }
.slide--b { background: linear-gradient(135deg, #2a0f12, var(--accent)); }
.slide--c { background: linear-gradient(135deg, #3a1f3a, #b14a8f); }
.carousel-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 2.8rem;
height: 2.8rem;
font-size: 1.6rem;
line-height: 1;
display: grid;
place-items: center;
background: var(--paper);
border: 3px solid var(--ink);
border-radius: 50%;
cursor: pointer;
box-shadow: var(--shadow-sm);
color: var(--ink);
font-weight: 700;
}
.carousel-arrow:hover { background: var(--accent-2); }
.carousel-arrow:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 2px; }
.carousel-arrow--prev { left: -0.8rem; }
.carousel-arrow--next { right: -0.8rem; }
.carousel-dots {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 0.9rem;
}
.dot {
width: 0.9rem;
height: 0.9rem;
border: 2px solid var(--ink);
border-radius: 50%;
background: var(--panel);
cursor: pointer;
padding: 0;
}
.dot.is-active { background: var(--accent); }
.dot:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 2px; }
/* ---------- controls ---------- */
.controls {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
justify-content: space-between;
margin-bottom: 0.4rem;
}
.chips {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.chip {
font-family: inherit;
font-weight: 600;
font-size: 0.88rem;
border: 2px solid var(--ink);
border-radius: 999px;
padding: 0.4rem 0.95rem;
background: var(--panel);
color: var(--ink);
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease, transform 0.1s ease;
}
.chip:hover { transform: translateY(-1px); background: var(--accent-2); }
.chip.is-active {
background: var(--ink);
color: var(--paper);
}
.chip:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 2px; }
.sort {
display: flex;
align-items: center;
gap: 0.5rem;
}
.sort-label {
font-weight: 700;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.06em;
}
#sortSelect {
font-family: inherit;
font-weight: 600;
font-size: 0.9rem;
border: 2px solid var(--ink);
border-radius: var(--r-sm);
padding: 0.45rem 0.7rem;
background: var(--panel);
color: var(--ink);
cursor: pointer;
}
#sortSelect:focus-visible { outline: 3px solid var(--accent-blue); outline-offset: 2px; }
.result-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
margin: 1rem 0 0.6rem;
}
.result-count {
margin: 0;
font-weight: 600;
color: var(--muted);
}
.result-count strong { color: var(--ink); }
/* ---------- grid ---------- */
.grid {
list-style: none;
margin: 0;
padding: 0 0 2.5rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.2rem;
}
.card {
display: flex;
flex-direction: column;
background: var(--panel);
border: 3px solid var(--ink);
border-radius: var(--r-md);
overflow: hidden;
box-shadow: var(--shadow-sm);
transition: transform 0.16s ease, box-shadow 0.16s ease;
}
.card:hover {
transform: translate(-3px, -4px);
box-shadow: 7px 8px 0 var(--ink);
}
.card-cover {
position: relative;
aspect-ratio: 3 / 4;
display: grid;
place-items: center;
background-image: var(--halftone);
background-size: 7px 7px;
border-bottom: 3px solid var(--ink);
}
.card-cover .cover-sfx {
font-family: "Bangers", cursive;
font-size: clamp(1.6rem, 5vw, 2.4rem);
color: #fff;
-webkit-text-stroke: 1.5px var(--ink);
transform: rotate(-7deg);
text-shadow: 3px 3px 0 var(--ink);
text-align: center;
padding: 0 0.5rem;
}
.card-badges {
position: absolute;
top: 0.55rem;
left: 0.55rem;
display: flex;
gap: 0.35rem;
}
.badge {
font-weight: 800;
font-size: 0.7rem;
letter-spacing: 0.08em;
padding: 0.18rem 0.45rem;
border: 2px solid var(--ink);
border-radius: var(--r-sm);
text-transform: uppercase;
}
.badge--new { background: var(--accent-blue); color: #fff; }
.badge--hot { background: var(--accent-2); color: var(--ink); }
.card-body {
padding: 0.75rem 0.85rem 0.9rem;
display: flex;
flex-direction: column;
gap: 0.35rem;
flex: 1;
}
.card-title {
margin: 0;
font-size: 1.05rem;
font-weight: 800;
line-height: 1.15;
}
.card-author {
margin: 0;
font-size: 0.82rem;
color: var(--muted);
font-weight: 500;
}
.card-foot {
margin-top: auto;
padding-top: 0.45rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.4rem;
}
.tag {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.2rem 0.5rem;
border: 2px solid var(--ink);
border-radius: 999px;
background: var(--paper);
}
.tag--genre { background: var(--accent-2); }
.rating {
font-weight: 700;
font-size: 0.85rem;
color: var(--ink);
white-space: nowrap;
}
.rating .star { color: var(--accent); }
.empty {
text-align: center;
padding: 2.5rem 1rem 3rem;
font-weight: 600;
color: var(--muted);
}
.empty-sfx {
display: block;
font-family: "Bangers", cursive;
font-size: 3rem;
color: var(--accent);
-webkit-text-stroke: 1.5px var(--ink);
transform: rotate(-6deg);
margin-bottom: 0.5rem;
}
/* ---------- footer ---------- */
.footer {
border-top: 3px solid var(--ink);
background: var(--panel);
}
.footer-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1.1rem 0;
flex-wrap: wrap;
}
.footer-inner p {
margin: 0;
color: var(--muted);
font-size: 0.85rem;
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 1.4rem;
transform: translate(-50%, 150%);
background: var(--ink);
color: var(--paper);
font-weight: 600;
padding: 0.7rem 1.2rem;
border-radius: var(--r-sm);
border: 2px solid var(--accent-2);
box-shadow: var(--shadow-sm);
z-index: 60;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
pointer-events: none;
max-width: min(90vw, 360px);
}
.toast.is-visible {
transform: translate(-50%, 0);
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}
/* ---------- responsive ---------- */
@media (max-width: 720px) {
.slide {
grid-template-columns: 1fr;
min-height: 0;
}
.slide-art {
border-left: none;
border-top: 3px solid var(--ink);
min-height: 120px;
order: -1;
}
.slide-copy { padding: 1.4rem 1.5rem; }
}
@media (max-width: 520px) {
.wrap { width: min(1140px, 100% - 1.5rem); }
.topnav { display: none; }
.controls { flex-direction: column; align-items: stretch; }
.sort { justify-content: space-between; }
.grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.8rem; }
.carousel-arrow--prev { left: 0.3rem; }
.carousel-arrow--next { right: 0.3rem; }
.brand-name { font-size: 1.4rem; }
}(function () {
"use strict";
/* ---------- toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 2200);
}
/* ---------- series data (fictional) ---------- */
var series = [
{ title: "Neon Ronin", author: "K. Ardent", genre: "Sci-Fi", rating: 4.9, added: 6, popularity: 98, badge: "HOT", sfx: "VWOOSH", hue: 220 },
{ title: "Iron Vanguard", author: "M. Solano", genre: "Action", rating: 4.7, added: 1, popularity: 91, badge: "NEW", sfx: "KA-BOOM", hue: 350 },
{ title: "Petals & Static", author: "R. Hale", genre: "Romance", rating: 4.8, added: 12, popularity: 95, badge: "HOT", sfx: "FLUTTER", hue: 320 },
{ title: "The Hollow Hour", author: "V. Crane", genre: "Horror", rating: 4.5, added: 3, popularity: 84, badge: "NEW", sfx: "CREAK", hue: 12 },
{ title: "Saltwater Saints", author: "D. Oyelaran", genre: "Slice of Life", rating: 4.6, added: 20, popularity: 77, badge: "", sfx: "SPLASH", hue: 190 },
{ title: "Glasswing", author: "T. Marsh", genre: "Fantasy", rating: 4.4, added: 9, popularity: 72, badge: "", sfx: "SHIMMER", hue: 150 },
{ title: "Lunchbox Heroes", author: "P. Quill", genre: "Comedy", rating: 4.3, added: 2, popularity: 69, badge: "NEW", sfx: "BONK", hue: 45 },
{ title: "Dead Channel", author: "V. Crane", genre: "Horror", rating: 4.7, added: 30, popularity: 88, badge: "", sfx: "STATIC", hue: 280 },
{ title: "Cinder & Vow", author: "R. Hale", genre: "Romance", rating: 4.2, added: 18, popularity: 64, badge: "", sfx: "SWOON", hue: 335 },
{ title: "Orbital Drift", author: "K. Ardent", genre: "Sci-Fi", rating: 4.6, added: 5, popularity: 81, badge: "HOT", sfx: "WHIRR", hue: 205 },
{ title: "The Last Innkeeper", author: "T. Marsh", genre: "Fantasy", rating: 4.8, added: 25, popularity: 90, badge: "", sfx: "CLANG", hue: 30 },
{ title: "Quarter Past Mayhem", author: "M. Solano", genre: "Action", rating: 4.1, added: 4, popularity: 60, badge: "NEW", sfx: "SMASH", hue: 0 }
];
/* ---------- state ---------- */
var activeGenres = new Set(["all"]);
var sortMode = "trending";
var grid = document.getElementById("seriesGrid");
var emptyState = document.getElementById("emptyState");
var resultCount = document.getElementById("resultCount");
var clearBtn = document.getElementById("clearBtn");
var chipBox = document.getElementById("genreChips");
var sortSelect = document.getElementById("sortSelect");
function buildCard(s) {
var li = document.createElement("li");
li.className = "card";
var cover = document.createElement("div");
cover.className = "card-cover";
cover.style.background =
"linear-gradient(135deg, hsl(" + s.hue + " 55% 30%), hsl(" + ((s.hue + 40) % 360) + " 70% 48%))";
cover.style.backgroundBlendMode = "multiply";
if (s.badge) {
var badges = document.createElement("div");
badges.className = "card-badges";
var b = document.createElement("span");
b.className = "badge " + (s.badge === "NEW" ? "badge--new" : "badge--hot");
b.textContent = s.badge;
badges.appendChild(b);
cover.appendChild(badges);
}
var sfx = document.createElement("span");
sfx.className = "cover-sfx";
sfx.textContent = s.sfx;
cover.appendChild(sfx);
var body = document.createElement("div");
body.className = "card-body";
var h3 = document.createElement("h3");
h3.className = "card-title";
h3.textContent = s.title;
var author = document.createElement("p");
author.className = "card-author";
author.textContent = "by " + s.author;
var foot = document.createElement("div");
foot.className = "card-foot";
var tag = document.createElement("span");
tag.className = "tag tag--genre";
tag.textContent = s.genre;
var rating = document.createElement("span");
rating.className = "rating";
rating.setAttribute("aria-label", "Rated " + s.rating + " of 5");
rating.innerHTML = '<span class="star">★</span> ' + s.rating.toFixed(1);
foot.appendChild(tag);
foot.appendChild(rating);
body.appendChild(h3);
body.appendChild(author);
body.appendChild(foot);
li.appendChild(cover);
li.appendChild(body);
li.addEventListener("click", function () {
toast("Opening “" + s.title + "” …");
});
return li;
}
function getFiltered() {
var list = series.filter(function (s) {
return activeGenres.has("all") || activeGenres.has(s.genre);
});
list.sort(function (a, b) {
if (sortMode === "new") return a.added - b.added;
if (sortMode === "rating") return b.rating - a.rating;
return b.popularity - a.popularity; // trending
});
return list;
}
function render() {
var list = getFiltered();
grid.innerHTML = "";
list.forEach(function (s) {
grid.appendChild(buildCard(s));
});
var hasResults = list.length > 0;
emptyState.hidden = hasResults;
grid.hidden = !hasResults;
var label = sortMode === "new" ? "Newest" : sortMode === "rating" ? "Top rated" : "Trending";
resultCount.innerHTML =
"<strong>" + list.length + "</strong> series · " + label;
clearBtn.hidden = activeGenres.has("all");
}
/* ---------- chip filtering (multi-select) ---------- */
chipBox.addEventListener("click", function (e) {
var chip = e.target.closest(".chip");
if (!chip) return;
var genre = chip.dataset.genre;
if (genre === "all") {
activeGenres = new Set(["all"]);
} else {
activeGenres.delete("all");
if (activeGenres.has(genre)) {
activeGenres.delete(genre);
} else {
activeGenres.add(genre);
}
if (activeGenres.size === 0) activeGenres.add("all");
}
syncChips();
render();
});
function syncChips() {
var chips = chipBox.querySelectorAll(".chip");
chips.forEach(function (c) {
var on = activeGenres.has(c.dataset.genre);
c.classList.toggle("is-active", on);
c.setAttribute("aria-pressed", on ? "true" : "false");
});
}
clearBtn.addEventListener("click", function () {
activeGenres = new Set(["all"]);
syncChips();
render();
toast("Filters cleared");
});
sortSelect.addEventListener("change", function () {
sortMode = sortSelect.value;
render();
toast("Sorted by " + sortSelect.options[sortSelect.selectedIndex].text);
});
/* ---------- read-now buttons ---------- */
document.querySelectorAll("[data-read]").forEach(function (btn) {
btn.addEventListener("click", function (e) {
e.stopPropagation();
toast("Opening “" + btn.dataset.read + "” …");
});
});
var signInBtn = document.getElementById("signInBtn");
if (signInBtn) {
signInBtn.addEventListener("click", function () {
toast("Sign-in is illustrative only");
});
}
/* ---------- carousel ---------- */
var track = document.getElementById("carouselTrack");
var slides = track ? track.querySelectorAll(".slide") : [];
var dotsBox = document.getElementById("carouselDots");
var prevBtn = document.getElementById("prevSlide");
var nextBtn = document.getElementById("nextSlide");
var current = 0;
var autoTimer;
var AUTO_MS = 5000;
function goTo(i) {
if (!slides.length) return;
current = (i + slides.length) % slides.length;
track.style.transform = "translateX(-" + current * 100 + "%)";
var dots = dotsBox.querySelectorAll(".dot");
dots.forEach(function (d, idx) {
d.classList.toggle("is-active", idx === current);
d.setAttribute("aria-selected", idx === current ? "true" : "false");
});
}
function next() { goTo(current + 1); }
function prev() { goTo(current - 1); }
function startAuto() {
stopAuto();
autoTimer = setInterval(next, AUTO_MS);
}
function stopAuto() {
clearInterval(autoTimer);
}
function restartAuto() {
startAuto();
}
if (slides.length) {
slides.forEach(function (_, i) {
var dot = document.createElement("button");
dot.type = "button";
dot.className = "dot" + (i === 0 ? " is-active" : "");
dot.setAttribute("role", "tab");
dot.setAttribute("aria-label", "Go to slide " + (i + 1));
dot.setAttribute("aria-selected", i === 0 ? "true" : "false");
dot.addEventListener("click", function () {
goTo(i);
restartAuto();
});
dotsBox.appendChild(dot);
});
nextBtn.addEventListener("click", function () { next(); restartAuto(); });
prevBtn.addEventListener("click", function () { prev(); restartAuto(); });
var carouselEl = track.closest(".carousel");
carouselEl.addEventListener("mouseenter", stopAuto);
carouselEl.addEventListener("mouseleave", startAuto);
carouselEl.addEventListener("focusin", stopAuto);
carouselEl.addEventListener("focusout", startAuto);
carouselEl.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight") { next(); restartAuto(); }
else if (e.key === "ArrowLeft") { prev(); restartAuto(); }
});
startAuto();
}
/* ---------- init ---------- */
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>INKWELL — Browse Comics</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=Bangers&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="topbar">
<div class="wrap topbar-inner">
<a class="brand" href="#" aria-label="Inkwell home">
<span class="brand-mark" aria-hidden="true">!</span>
<span class="brand-name">INKWELL</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#" class="is-active">Browse</a>
<a href="#">Library</a>
<a href="#">Releases</a>
<a href="#">Creators</a>
</nav>
<button class="btn btn-ghost" id="signInBtn" type="button">Sign in</button>
</div>
</header>
<main id="main" class="wrap">
<!-- Featured carousel -->
<section class="carousel" aria-label="Featured series" aria-roledescription="carousel">
<div class="carousel-viewport">
<ul class="carousel-track" id="carouselTrack">
<li class="slide slide--a" role="group" aria-roledescription="slide" aria-label="1 of 3">
<div class="slide-copy">
<span class="slide-kicker">Featured this week</span>
<h2 class="slide-title">Neon Ronin</h2>
<p class="slide-sub">A masterless swordsman wanders a rain-soaked megacity, trading silence for blood debts.</p>
<div class="slide-meta">
<span class="tag tag--genre">Sci-Fi</span>
<span class="rating" aria-label="Rated 4.9 of 5">★ 4.9</span>
<span class="badge badge--hot">HOT</span>
</div>
<button class="btn btn-accent" type="button" data-read="Neon Ronin">Read now</button>
</div>
<div class="slide-art" aria-hidden="true"><span class="sfx">VWOOSH</span></div>
</li>
<li class="slide slide--b" role="group" aria-roledescription="slide" aria-label="2 of 3">
<div class="slide-copy">
<span class="slide-kicker">New arc dropped</span>
<h2 class="slide-title">Iron Vanguard</h2>
<p class="slide-sub">When the colony shields fail, one squad of misfit mechs is all that stands between the void and the dome.</p>
<div class="slide-meta">
<span class="tag tag--genre">Action</span>
<span class="rating" aria-label="Rated 4.7 of 5">★ 4.7</span>
<span class="badge badge--new">NEW</span>
</div>
<button class="btn btn-accent" type="button" data-read="Iron Vanguard">Read now</button>
</div>
<div class="slide-art" aria-hidden="true"><span class="sfx">KA-BOOM</span></div>
</li>
<li class="slide slide--c" role="group" aria-roledescription="slide" aria-label="3 of 3">
<div class="slide-copy">
<span class="slide-kicker">Reader favourite</span>
<h2 class="slide-title">Petals & Static</h2>
<p class="slide-sub">A radio host and a florist keep missing each other by minutes — until a storm reroutes both their nights.</p>
<div class="slide-meta">
<span class="tag tag--genre">Romance</span>
<span class="rating" aria-label="Rated 4.8 of 5">★ 4.8</span>
<span class="badge badge--hot">HOT</span>
</div>
<button class="btn btn-accent" type="button" data-read="Petals & Static">Read now</button>
</div>
<div class="slide-art" aria-hidden="true"><span class="sfx">FLUTTER</span></div>
</li>
</ul>
</div>
<button class="carousel-arrow carousel-arrow--prev" id="prevSlide" type="button" aria-label="Previous slide">‹</button>
<button class="carousel-arrow carousel-arrow--next" id="nextSlide" type="button" aria-label="Next slide">›</button>
<div class="carousel-dots" id="carouselDots" role="tablist" aria-label="Choose slide"></div>
</section>
<!-- Controls -->
<section class="controls" aria-label="Filter and sort">
<div class="chips" id="genreChips" role="group" aria-label="Filter by genre">
<button class="chip is-active" type="button" data-genre="all" aria-pressed="true">All</button>
<button class="chip" type="button" data-genre="Action" aria-pressed="false">Action</button>
<button class="chip" type="button" data-genre="Romance" aria-pressed="false">Romance</button>
<button class="chip" type="button" data-genre="Horror" aria-pressed="false">Horror</button>
<button class="chip" type="button" data-genre="Sci-Fi" aria-pressed="false">Sci-Fi</button>
<button class="chip" type="button" data-genre="Fantasy" aria-pressed="false">Fantasy</button>
<button class="chip" type="button" data-genre="Slice of Life" aria-pressed="false">Slice of Life</button>
<button class="chip" type="button" data-genre="Comedy" aria-pressed="false">Comedy</button>
</div>
<label class="sort">
<span class="sort-label">Sort</span>
<select id="sortSelect" aria-label="Sort series">
<option value="trending">Trending</option>
<option value="new">New</option>
<option value="rating">Top rated</option>
</select>
</label>
</section>
<div class="result-bar">
<p id="resultCount" class="result-count" role="status" aria-live="polite"></p>
<button class="btn btn-ghost btn-sm" id="clearBtn" type="button" hidden>Clear filters</button>
</div>
<!-- Series grid -->
<section aria-label="Series">
<ul class="grid" id="seriesGrid"></ul>
<p class="empty" id="emptyState" hidden>
<span class="empty-sfx" aria-hidden="true">POW?</span>
No series match those genres yet. Try clearing a filter.
</p>
</section>
</main>
<footer class="footer">
<div class="wrap footer-inner">
<span class="brand-name">INKWELL</span>
<p>Illustrative UI only — fictional series, characters, and data.</p>
</div>
</footer>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Browse / Discover (genre grid)
A browse-and-discover front page for Inkwell, a fictional comics platform, styled in classic comic-book grammar: thick ink panel borders, Ben-Day halftone dot textures, bold uppercase SFX lettering set in Bangers, and a high-contrast ink-on-paper palette with red, gold, and blue accents. A sticky masthead carries the brand mark and primary nav above a featured carousel banner that rotates three hero series.
The carousel auto-advances every five seconds, pauses on hover or keyboard focus, and is driven by previous/next arrows, a row of dot tabs, and left/right arrow keys. Below it, a strip of multi-select genre chips (Action, Romance, Horror, Sci-Fi, Fantasy, Slice of Life, Comedy) filters a responsive grid of series cards, while a sort dropdown reorders the results by Trending, New, or Top rated. Each card pairs a gradient cover with halftone SFX, a title, author, genre tag, star rating, and an optional NEW or HOT badge, lifting on hover for a tactile feel.
Filtering, sorting, and clearing all run in vanilla JavaScript with no dependencies — the grid re-renders live, a status line reports the count and active sort, an empty state appears when no series match, and a small toast helper confirms each interaction with aria-live feedback. Every control is keyboard-usable and labelled for assistive tech.
Illustrative UI only — fictional series, characters, and data.