Comics — Panel Frame + Gutter System
A reusable comic-page layout kit for the fictional series Neon Ronin, built entirely with CSS grid, hard ink borders, and Ben-Day halftone fills. Four preset arrangements — a 2x2 grid, a splash panel over a three-beat strip, skewed diagonal panels, and a floating inset panel — swap with a smooth transition from a comic-styled button bar. Every panel carries a duotone gradient, an uppercase SFX or tailed speech balloon, and a yellow caption box. A live gutter slider drives a CSS variable, retuning the white space between panels in real time with toast feedback.
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;
/* live-tuned by JS */
--gutter: 14px;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}
body {
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;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 28px 20px 56px;
}
/* ---------- Masthead ---------- */
.masthead {
display: flex;
align-items: center;
gap: 16px;
background: var(--panel);
border: 3px solid var(--ink);
border-radius: var(--r-lg);
padding: 16px 20px;
box-shadow: 8px 8px 0 var(--ink);
}
.masthead-mark {
font-family: "Bangers", cursive;
font-size: 2.6rem;
letter-spacing: 1px;
line-height: 1;
color: var(--paper);
background: var(--accent);
border: 3px solid var(--ink);
border-radius: var(--r-md);
padding: 6px 12px 2px;
transform: rotate(-4deg);
box-shadow: 3px 3px 0 var(--ink);
}
.masthead-titles {
flex: 1;
min-width: 0;
}
.masthead-titles h1 {
font-family: "Bangers", cursive;
font-weight: 400;
font-size: clamp(2rem, 6vw, 3.4rem);
letter-spacing: 1.5px;
margin: 0;
line-height: 1;
color: var(--ink);
-webkit-text-stroke: 0.6px var(--ink);
}
.tagline {
margin: 6px 0 0;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--muted);
}
.masthead-badge {
align-self: flex-start;
font-size: 0.66rem;
font-weight: 800;
letter-spacing: 1.5px;
color: var(--ink);
background: var(--accent-2);
border: 2px solid var(--ink);
border-radius: var(--r-sm);
padding: 5px 8px;
transform: rotate(3deg);
white-space: nowrap;
}
/* ---------- Controls ---------- */
.controls {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 18px 28px;
margin-top: 24px;
background: var(--panel);
border: 3px solid var(--ink);
border-radius: var(--r-lg);
padding: 16px 18px;
box-shadow: 6px 6px 0 var(--ink);
}
.control-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.control-slider {
flex: 1;
min-width: 200px;
}
.control-label {
font-size: 0.72rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--ink-2);
}
.gutter-readout {
display: inline-block;
margin-left: 4px;
color: var(--accent);
font-variant-numeric: tabular-nums;
}
.layout-switch {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.layout-btn {
font-family: "Inter", sans-serif;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.3px;
color: var(--ink);
background: var(--paper);
border: 2px solid var(--ink);
border-radius: var(--r-sm);
padding: 9px 13px;
cursor: pointer;
box-shadow: 3px 3px 0 var(--ink);
transition: transform 0.08s ease, box-shadow 0.08s ease, background 0.12s ease, color 0.12s ease;
}
.layout-btn:hover {
background: var(--accent-2);
}
.layout-btn:active {
transform: translate(3px, 3px);
box-shadow: 0 0 0 var(--ink);
}
.layout-btn:focus-visible {
outline: 3px solid var(--accent-blue);
outline-offset: 2px;
}
.layout-btn.is-active {
background: var(--ink);
color: var(--paper);
}
.layout-btn.is-active:hover {
background: var(--ink-2);
}
/* range slider */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 12px;
background: var(--paper);
border: 2px solid var(--ink);
border-radius: 999px;
cursor: pointer;
}
input[type="range"]:focus-visible {
outline: 3px solid var(--accent-blue);
outline-offset: 3px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--accent);
border: 3px solid var(--ink);
box-shadow: 2px 2px 0 var(--ink);
margin-top: -1px;
}
input[type="range"]::-moz-range-thumb {
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--accent);
border: 3px solid var(--ink);
box-shadow: 2px 2px 0 var(--ink);
}
/* ---------- Page stage ---------- */
.page-stage {
margin-top: 24px;
}
.page {
--rows: 2;
display: grid;
gap: var(--gutter);
aspect-ratio: 3 / 4;
width: 100%;
padding: var(--gutter);
background: var(--ink);
border: 3px solid var(--ink);
border-radius: var(--r-lg);
box-shadow: 10px 10px 0 var(--ink);
transition: gap 0.15s ease, padding 0.15s ease;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-template-areas:
"a b"
"c d";
}
/* ---------- Panels ---------- */
.panel {
position: relative;
margin: 0;
overflow: hidden;
display: flex;
align-items: flex-end;
background-color: var(--panel);
background-image: var(--halftone);
background-size: 6px 6px;
border: 3px solid var(--ink);
border-radius: var(--r-sm);
min-height: 0;
transition: transform 0.28s cubic-bezier(0.2, 0.9, 0.3, 1), clip-path 0.28s ease, box-shadow 0.18s ease;
}
.panel::before {
content: "";
position: absolute;
inset: 0;
opacity: 0.9;
z-index: 0;
background: linear-gradient(150deg, var(--g1, #ffe7ec) 0%, var(--g2, #ffd23f) 100%);
mix-blend-mode: multiply;
}
.panel:hover {
box-shadow: inset 0 0 0 3px var(--accent);
}
.panel-a { grid-area: a; --g1: #ffd6dd; --g2: #ff8aa0; }
.panel-b { grid-area: b; --g1: #d8e3ff; --g2: #2e6bff; }
.panel-c { grid-area: c; --g1: #fff0c2; --g2: #ffd23f; }
.panel-d { grid-area: d; --g1: #1c1c24; --g2: #5a5a72; }
/* ---------- SFX + balloons ---------- */
.sfx {
position: absolute;
top: 14px;
left: 14px;
z-index: 2;
font-family: "Bangers", cursive;
font-size: clamp(1.6rem, 5vw, 2.8rem);
letter-spacing: 1px;
line-height: 0.9;
color: var(--accent-2);
-webkit-text-stroke: 2px var(--ink);
text-shadow: 3px 3px 0 var(--ink);
transform: rotate(-6deg);
pointer-events: none;
}
.sfx-blue {
color: var(--accent-blue);
transform: rotate(5deg);
}
.balloon {
position: absolute;
top: 14px;
left: 14px;
right: 14px;
z-index: 2;
font-size: 0.82rem;
font-weight: 600;
color: var(--ink);
background: var(--paper);
border: 2.5px solid var(--ink);
border-radius: 16px;
padding: 9px 12px;
max-width: min(78%, 230px);
box-shadow: 3px 3px 0 var(--ink);
}
.balloon::after {
content: "";
position: absolute;
bottom: -14px;
left: 24px;
width: 18px;
height: 18px;
background: var(--paper);
border-right: 2.5px solid var(--ink);
border-bottom: 2.5px solid var(--ink);
transform: rotate(35deg) skew(-8deg);
}
.balloon-shout {
font-family: "Bangers", cursive;
font-weight: 400;
font-size: 1.15rem;
letter-spacing: 0.8px;
text-align: center;
border-radius: 6px;
clip-path: polygon(
0% 16%, 12% 0%, 30% 12%, 48% 0%, 66% 12%, 84% 0%, 100% 16%,
92% 50%, 100% 84%, 84% 100%, 66% 88%, 48% 100%, 30% 88%, 12% 100%, 0% 84%, 8% 50%
);
padding: 16px 18px;
}
.balloon-shout::after {
display: none;
}
.caption {
position: relative;
z-index: 1;
margin: 12px;
font-size: 0.74rem;
font-weight: 600;
line-height: 1.35;
color: var(--ink);
background: var(--accent-2);
border: 2px solid var(--ink);
border-radius: var(--r-sm);
padding: 7px 9px;
box-shadow: 2px 2px 0 var(--ink);
}
.panel-d .caption {
background: var(--paper);
}
/* ---------- Layout presets ---------- */
.page[data-layout="grid"] {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-template-areas:
"a b"
"c d";
}
.page[data-layout="splash"] {
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1.6fr 1fr;
grid-template-areas:
"a a a"
"b c d";
}
.page[data-layout="diagonal"] {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-template-areas:
"a b"
"c d";
}
.page[data-layout="diagonal"] .panel-a {
clip-path: polygon(0 0, 100% 0, 100% 70%, 0 100%);
}
.page[data-layout="diagonal"] .panel-b {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 28%);
}
.page[data-layout="diagonal"] .panel-c {
clip-path: polygon(0 28%, 100% 0, 100% 100%, 0 100%);
}
.page[data-layout="diagonal"] .panel-d {
clip-path: polygon(0 0, 100% 28%, 100% 100%, 0 100%);
}
.page[data-layout="inset"] {
position: relative;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-template-areas:
"a a"
"c d";
}
.page[data-layout="inset"] .panel-b {
position: absolute;
top: 12%;
right: 8%;
width: 34%;
height: 38%;
z-index: 5;
transform: rotate(3deg);
box-shadow: 5px 5px 0 var(--ink);
}
/* ---------- Legend ---------- */
.legend {
display: flex;
align-items: flex-start;
gap: 12px;
margin-top: 26px;
padding: 14px 16px;
background: var(--panel);
border: 2px dashed var(--line-2);
border-radius: var(--r-md);
}
.legend-dot {
flex: none;
width: 16px;
height: 16px;
margin-top: 2px;
border-radius: 50%;
background: var(--accent);
border: 2px solid var(--ink);
}
.legend p {
margin: 0;
font-size: 0.86rem;
color: var(--ink-2);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translate(-50%, 24px);
z-index: 50;
font-weight: 700;
font-size: 0.85rem;
letter-spacing: 0.3px;
color: var(--paper);
background: var(--ink);
border: 2px solid var(--ink);
border-radius: var(--r-md);
padding: 11px 18px;
box-shadow: 4px 4px 0 var(--accent);
opacity: 0;
pointer-events: none;
transition: transform 0.22s ease, opacity 0.22s ease;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 520px) {
.wrap {
padding: 18px 12px 44px;
}
.masthead {
flex-wrap: wrap;
box-shadow: 5px 5px 0 var(--ink);
}
.masthead-badge {
order: 3;
}
.controls {
flex-direction: column;
align-items: stretch;
box-shadow: 4px 4px 0 var(--ink);
}
.layout-btn {
flex: 1 1 calc(50% - 8px);
text-align: center;
}
.page {
aspect-ratio: 4 / 5;
box-shadow: 6px 6px 0 var(--ink);
}
.page[data-layout="splash"] {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1.4fr 1fr;
grid-template-areas:
"a a"
"b c";
}
.page[data-layout="splash"] .panel-d {
display: none;
}
.sfx {
font-size: 1.5rem;
}
.balloon {
font-size: 0.74rem;
max-width: 88%;
}
}
@media (prefers-reduced-motion: reduce) {
.panel,
.page,
.layout-btn,
.toast {
transition: none;
}
}(function () {
"use strict";
const page = document.getElementById("page");
const switchEl = document.getElementById("layoutSwitch");
const buttons = Array.from(switchEl.querySelectorAll(".layout-btn"));
const gutter = document.getElementById("gutter");
const gutterVal = document.getElementById("gutterVal");
const toastEl = document.getElementById("toast");
const LAYOUT_NAMES = {
grid: "2 × 2 Grid",
splash: "Splash + Strip",
diagonal: "Diagonal Panels",
inset: "Inset Panel",
};
/* ---- toast helper ---- */
let toastTimer = null;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
}, 1900);
}
/* ---- layout switcher ---- */
function setLayout(layout) {
if (!LAYOUT_NAMES[layout] || page.dataset.layout === layout) return;
page.dataset.layout = layout;
buttons.forEach(function (btn) {
const active = btn.dataset.layout === layout;
btn.classList.toggle("is-active", active);
btn.setAttribute("aria-pressed", active ? "true" : "false");
});
toast("Layout → " + LAYOUT_NAMES[layout]);
}
switchEl.addEventListener("click", function (e) {
const btn = e.target.closest(".layout-btn");
if (btn) setLayout(btn.dataset.layout);
});
/* arrow-key navigation across the layout buttons */
switchEl.addEventListener("keydown", function (e) {
const idx = buttons.indexOf(document.activeElement);
if (idx === -1) return;
let next = -1;
if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (idx + 1) % buttons.length;
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (idx - 1 + buttons.length) % buttons.length;
if (next === -1) return;
e.preventDefault();
buttons[next].focus();
setLayout(buttons[next].dataset.layout);
});
/* ---- gutter slider -> CSS variable ---- */
function applyGutter(px) {
page.style.setProperty("--gutter", px + "px");
gutterVal.textContent = px + "px";
}
gutter.addEventListener("input", function () {
applyGutter(parseInt(gutter.value, 10));
});
let releaseTimer = null;
gutter.addEventListener("change", function () {
clearTimeout(releaseTimer);
releaseTimer = setTimeout(function () {
toast("Gutter set to " + gutter.value + "px");
}, 120);
});
/* ---- init ---- */
applyGutter(parseInt(gutter.value, 10));
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Comics — Panel Frame + Gutter System</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>
<div class="wrap">
<header class="masthead">
<div class="masthead-mark" aria-hidden="true">#42</div>
<div class="masthead-titles">
<h1>Neon Ronin</h1>
<p class="tagline">Panel Frame & Gutter System — issue layout kit</p>
</div>
<div class="masthead-badge" aria-hidden="true">SUNDAY EDITION</div>
</header>
<section class="controls" aria-label="Page layout controls">
<div class="control-group" role="group" aria-label="Panel layouts">
<span class="control-label">Layout</span>
<div class="layout-switch" id="layoutSwitch">
<button type="button" class="layout-btn is-active" data-layout="grid" aria-pressed="true">2 × 2 Grid</button>
<button type="button" class="layout-btn" data-layout="splash" aria-pressed="false">Splash + Strip</button>
<button type="button" class="layout-btn" data-layout="diagonal" aria-pressed="false">Diagonal</button>
<button type="button" class="layout-btn" data-layout="inset" aria-pressed="false">Inset</button>
</div>
</div>
<div class="control-group control-slider">
<label class="control-label" for="gutter">Gutter <span id="gutterVal" class="gutter-readout">14px</span></label>
<input type="range" id="gutter" min="0" max="40" step="2" value="14" aria-describedby="gutterVal" />
</div>
</section>
<main class="page-stage">
<div class="page" id="page" data-layout="grid" aria-label="Comic page preview">
<figure class="panel panel-a">
<span class="sfx" aria-hidden="true">KRAK!</span>
<figcaption class="caption">PG.01 — The Ronin lands on the spire as sirens bleed neon into the rain.</figcaption>
</figure>
<figure class="panel panel-b">
<span class="balloon">Stay back. The grid is mine now.</span>
<figcaption class="caption">A whisper to the drones circling the rooftop.</figcaption>
</figure>
<figure class="panel panel-c">
<span class="sfx sfx-blue" aria-hidden="true">VWOOM</span>
<figcaption class="caption">Iron Vanguard powers up across the canyon of glass.</figcaption>
</figure>
<figure class="panel panel-d">
<span class="balloon balloon-shout">YOU CAN'T STOP US ALL!</span>
<figcaption class="caption">PG.04 — Two legends, one collapsing skyline.</figcaption>
</figure>
</div>
</main>
<footer class="legend">
<span class="legend-dot" aria-hidden="true"></span>
<p>Each panel is a CSS grid cell with a hard ink border and halftone fill. The active preset and gutter width are stored as CSS variables and swapped live — no images, no build step.</p>
</footer>
</div>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Panel Frame + Gutter System
A self-contained comic-page layout kit dressed up as issue #42 of the fictional series Neon Ronin. The page is a single CSS grid whose cells are bordered panels — thick 3px ink frames over a dark “gutter” backdrop, each panel filled with a Ben-Day halftone texture and a duotone gradient, then topped with bold uppercase SFX lettering, tailed speech balloons, and a yellow caption strip. The masthead uses Bangers for display lettering while Inter carries the UI and body copy.
A comic-styled button bar switches between four canonical page arrangements: a classic 2 × 2 grid, a splash panel over a three-beat bottom strip, skewed diagonal panels that read on a slant, and a layout with a small inset panel floating over a hero shot. The change is driven by a single data-layout attribute on the page, so the transition between presets is smooth and purely CSS. The layout buttons are keyboard navigable with arrow keys and announce each switch through a toast.
A gutter slider writes its value straight into a --gutter CSS custom property, retuning both the gaps between panels and the page padding live — drag it to zero for a borderless bleed or open it up for airy European-style spacing. Everything is vanilla: no images, no frameworks, no build step, and the whole kit collapses gracefully down to roughly 360px wide.
Illustrative UI only — fictional series, characters, and data.