00
// field notes from the wall

a6c.dev

A scroll-painted tutorial zine for developers who read the manual and still write on it.

01
Chapter 01 // Read Me First

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.

02
Chapter 02 // Stencil the State

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.

stencil.js
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."
03
Chapter 03 // Aerosol Async

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.

aerosol.js
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.
04
Chapter 04 // Wheat-paste Layouts

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.

wheatpaste.css
.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.

05
Chapter 05 // Tagging the DOM

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.

magnetic.js
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.

06
Chapter 06 // Sign Off

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