An anti-design zine with a spine
MAXIMAL is issue #001 of a fictional design zine arguing — loudly — against minimalism. Four essays, two fake ads, one escalation button. The aesthetic school is controlled chaos: the punk-zine and anti-design lineage of David Carson's Ray Gun, Emigre, GeoCities, and the ransom-note flyer on a phone pole — but built on modern CSS with the readability rules of a broadsheet.
The trick the whole site demonstrates: maximalism only reads as confident when there's a system underneath. Clashing colors, four typefaces, rotated overlapping blocks — and yet line lengths never pass 68 characters, body contrast stays AA, and every block snaps to the same 12-column grid. The chaos is a costume. The skeleton is Swiss.
- #ff2e88 hot pink
- #b6ff2e acid green
- #2ee6ff cyan
- #7b2eff purple
- #ffe62e yellow
- #0d0d0d ink
- #ffffff paper
Five loud values plus black and white — never more. Rainbow soup is what happens when maximalism has no palette; this is clashing on purpose, with the same five crayons every time.
Chaos with a grid underneath
gridRotate the block, never the column
Every section is placed on an honest 12-column grid, then rotated as a whole. The rotation is a costume applied after layout, so measure and rhythm survive. Overlaps come from negative margins plus z-index, not absolute positioning — the document still flows, and mobile just stacks everything back to 1/13.
/* the confessed grid */
.page { display: grid; grid-template-columns: repeat(12, 1fr); }
/* placement = grid; attitude = one transform on top */
#comic-sans { grid-column: 1/9; --r: 1.2deg; }
#ad-glitter { grid-column: 8/13; --r: -2.6deg; margin-top: -4rem; }
.tilt { transform: rotate(calc(var(--r) * var(--rot-scale) + var(--r-add, 0deg))); }
Three variables do all the work: --r is the block's personality (kept between −3° and 5°), --rot-scale drops to .35 under 640px so narrow columns don't clip, and --r-add is reserved for the escalation machine — level 4 politely adds exactly one more degree to one section. The colophon's “show the grid” button just unhides a fixed overlay built with the same wrapper, columns, and gap: proof you can render.
typeFour families, one job each
Alfa Slab One shouts (display), Archivo talks (body — its variable 100–900 axis also powers the fake font ad's weight slider), Fraunces feels (the confession essay and pull-quote italics), Space Mono annotates (metadata, colophon, fine print). Four typefaces read as chaos only if they compete for the same role; give each a beat and the ear hears a band, not a brawl.
The MORE!! escalation state machine
The signature interaction is one integer. The current chaos level lives in a tiny state object; rendering it is just toggling body classes, so CSS owns every visual change and JS owns only the side effects that spawn things.
var chaos = { level: 0 };
var onEnter = { 1: rainStickers, 3: startTrail, 5: finale };
function applyLevelClasses() {
for (var i = 1; i <= 5; i++)
body.classList.toggle('chaos-' + i, chaos.level >= i);
}
- LV 1 a dozen stickers rain in (Web Animations API drop) and stick where they land, in a fixed pointer-events-none layer
- LV 2 pure CSS:
body.chaos-2hardens headline shadows and sets--marquee-durfrom 22s to 7s - LV 3 a throttled
pointermovelistener spawns a trailing echo of tiny shapes (max 18 alive at once) - LV 4 pure CSS again: one section gets
--r-add: 1deg; one ad getsscale: 1.06— polite, specific, ridiculous - LV 5 a capped 64-piece confetti burst; the button, exhausted, becomes “ENOUGH.” and offers undo everything
Because levels are cumulative body classes, reset is honest: set the integer to 0, re-toggle classes (CSS transitions ease every rotation and shadow back — the “deflate”), remove the trail listener, and fade-and-remove() every spawned node. Nothing leaks. Pressing the button five times and undoing leaves the DOM exactly as it started.
// reset: one integer down, everything else follows
chaos.level = 0;
applyLevelClasses(); // CSS transitions = the deflate
stopTrail(); // removeEventListener + clear layer
stickers.forEach(fadeOutThenRemove);
Sticker generation & particles that behave
svgStarbursts from polar coordinates
Every starburst — the hand-placed “NEW!” badges and the level-1 rain — comes from one function. Walk a circle in 2 × spikes steps, alternating outer and inner radius, and you get a ransom-note star for free. Text sits at dead center with dominant-baseline: central.
function starPoints(spikes, outer, inner, cx, cy) {
var pts = [];
for (var i = 0; i < spikes * 2; i++) {
var r = (i % 2 === 0) ? outer : inner;
var a = (Math.PI * i) / spikes - Math.PI / 2;
pts.push((cx + r*Math.cos(a)).toFixed(1) + ',' + (cy + r*Math.sin(a)).toFixed(1));
}
return pts.join(' ');
}
perfCaps, transforms, and self-cleaning
Every particle system on the page — glitter demo, cursor trail, confetti — follows three rules: a hard cap on live nodes (26 / 18 / 64), animate only transform and opacity (compositor-friendly, no layout thrash), and self-destruct via the Web Animations API's onfinish so cleanup is guaranteed by the same object that animated.
var a = bit.animate([
{ transform: 'translate(-50%,-50%) scale(1)', opacity: 1 },
{ transform: 'translate(…) scale(0) rotate(…)', opacity: 0 }
], { duration: 700, easing: 'cubic-bezier(.2,.6,.3,1)' });
a.onfinish = function () { bit.remove(); }; // no orphans, ever
Keeping type readable inside maximalism
- Contrast is non-negotiable. Body text is only ever ink-on-paper or near-white-on-ink. The five loud colors live on chips, borders, shadows, and display type. Every ransom-note chip pairing was checked: black text on pink/acid/cyan/yellow, white text only on purple and ink.
- Jitter one word at a time. Headlines are split into per-word spans; the shear animation runs on
:hoverof a single word, wrapped in@media (hover:hover) and (prefers-reduced-motion: no-preference). The line you're reading never moves — only the word you're poking. - Rotation has a budget. −3° to 5°, scaled to 35% on phones. Past ~5°, line starts stop aligning with the reading eye's sweep and comprehension drops. The chaos is in the composition, not the sentences.
- Reduced motion = same page, zero motion. The composition survives intact — all rotations, colors, and stickers — but marquees freeze, reveals render instantly, hover jitter is off, and the MORE button skips rain/trail/confetti animations while still applying every level's static styling.
By hand, on purpose
MAXIMAL was built by Claude (Fable 5) writing vanilla HTML, CSS, and JavaScript by hand — one file per page, no frameworks, no build step, no dependencies. The fonts arrive via the Google Fonts CSS API; everything else, including every sticker, is generated in the page.
It's one of 25 wildly different sites in the Fable Showcase, an experiment in how far hand-written vanilla web pages can go. See the rest at fable-25-dhb.pages.dev.
Take it. We insist. That's the whole point.
- Rotate blocks, not layouts. Place everything on a boring grid, then apply
transform: rotate()to the finished block through a CSS variable. You get zine energy with none of the layout debt — and one media query calms it on mobile. - Make state an integer, render it as classes. Any “escalating” UI — onboarding steps, celebration tiers, theme intensities — is easiest as one number mapped to cumulative body classes. CSS does the styling; JS only handles spawn/despawn side effects.
- Give every particle a cap and an
onfinish. If a decoration can't tell you its maximum live count and who deletes it, it will eventually eat a phone. WAAPI'sonfinishmakes the animation itself the janitor. - Loud colors on chips, calm colors on paragraphs. You can clash five neons all day if body text never sits on one that fails AA. Decorative maximalism and accessible reading are not in conflict — they're different layers.
- Confess your grid. A “show the grid” toggle costs 15 lines and turns your flashiest choices into demonstrated skill. If the chaos is deliberate, prove it.