a6c.dev
A scroll-painted tutorial zine for developers who read the manual and still write on it.
Hold the can.
Shake it.
Read the label.
Welcome to a6c.dev — a long-form, single-scroll field manual for developers who learn by doing, break things on purpose, and document the dent. Every chapter here is a wall. Every wall is a lesson.
There is no navigation, no hamburger, no sticky mega-menu. Just gravity, scroll, and a thin burgundy line on the left that tells you how deep you are. Hover the left edge to see the chapter index surface like a fresh wheat-paste.
Everything visible here — the drifting paint mist, the magnetic links, the dripping code blocks — is built with just the platform: a single HTML file, a single stylesheet, a single script. No framework. No build step. Open DevTools. The wall is yours to read.
State is a stencil
you cut once
and spray twice.
Before you paint the UI, cut the stencil. State should be predictable, reducible, and boring. If reading your reducer feels like graffiti, you did it wrong. If reading your component feels like graffiti, you did it right.
The trick is separating the shape from the stroke. Shape is the stencil — the data model, the transitions, the invariants. Stroke is the render — the colors, the motion, the texture. Mix them and the wall looks muddy.
const initial = { layer: 'base', drips: 0, tagged: false };
function reduce(state, action) {
switch (action.type) {
case 'SPRAY':
return { ...state, drips: state.drips + 1 };
case 'TAG':
return { ...state, tagged: true, layer: action.name };
default:
return state;
}
}
A good reducer is a good stencil: cut once, spray often, always the same outline. If you find yourself editing the stencil every time you paint, that's a smell. Move the change into the stroke.
"If your state machine looks like handwriting,
redraw the stencil, not the paint."
Promises drift
like paint mist.
Catch them upwind.
Async code is aerosol: it leaves the nozzle, drifts on the wind, and lands somewhere you didn't plan. A promise chain without error handling is a cloud of overspray. Own the boundary.
async function spray(url, color) {
try {
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({ color, at: Date.now() }),
});
if (!res.ok) throw new Error('wall rejected the paint');
return await res.json();
} catch (err) {
report(err);
return { ok: false, reason: err.message };
}
}
Every async call is a can you hand off. If the handoff is awaited, your
error boundary is obvious. If it's fire-and-forget, wrap it in
.catch(report) so the cloud doesn't drift
silently into production.
- ▸ Await at the edge. Handle at the edge. Return data, not promises.
- ▸ Never swallow. Always report. Overspray is still paint on someone.
- ▸ Cancel long drifts with an
AbortController.
Grid is a wall.
Flex is a poster.
Paste them both.
CSS Grid is how you plan the wall. Flexbox is how you hang each poster. Mixing them is not a sin — it's how real walls get layered. The trick is knowing which layer you're pasting onto.
Use grid for two-axis structure — the overall mural. Use flex inside each cell for the single-axis arrangement — the content of one poster. Anything else is muddy overlap.
.mural {
display: grid;
grid-template-columns: minmax(16ch, 22ch) 1fr;
gap: 2.4rem 3.2rem;
}
.poster {
display: flex;
flex-direction: column;
gap: 0.8rem;
transform: rotate(-0.6deg);
}
"Grid picks the block.
Flex picks the pose.
Never ask flex to plan the city."
The ragged right edge of this paragraph is intentional. Graffiti does not
justify itself — it runs up to the limit, hits brick, and stops. Your
layouts can, too. Let text-wrap: pretty
handle the flow and resist the urge to center every last line.
Every element
is a wall
waiting for a tag.
Interaction is not hover. Interaction is pull. Every link here gravitates toward your cursor within an 80-pixel proximity — not as a gimmick, but to teach that the DOM is a physical surface. Move the cursor and feel it.
The system is twelve lines of JavaScript and one CSS transition. Nothing
more. It listens to mousemove, measures
distance, and applies a transform proportional to proximity. On leave, the
transform springs home. That is a hover state, reinvented.
const R = 80;
document.querySelectorAll('[data-magnetic]').forEach((el) => {
window.addEventListener('mousemove', (e) => {
const b = el.getBoundingClientRect();
const cx = b.left + b.width / 2;
const cy = b.top + b.height / 2;
const dx = e.clientX - cx;
const dy = e.clientY - cy;
const d = Math.hypot(dx, dy);
if (d < R) {
const k = (R - d) / R;
el.style.transform = `translate(${dx * k * 0.25}px, ${dy * k * 0.25}px)`;
} else {
el.style.transform = '';
}
});
});
The cursor is not a pointer. It is a magnet. Once you accept that, the entire interaction vocabulary changes — no more underlines, no more scale-on-hover, no more color flashes. Just physics and intent.
Cap the can.
Leave the wall.
Sign the bottom.
You scrolled to the bottom. That means you read, or at least looked. Either way, thanks. Graffiti is a conversation between the writer and anyone who cared enough to stop. This page was that.
If the drifting particles, the magnetic links, or the dripping code made you grin — good. That was the point. If they made you open DevTools and try to break them — even better. The source is three files. Go write your own wall.
— a6c.dev scrolled & signed, 2026