diff --git a/.specs/content-columns.md b/.specs/content-columns.md
new file mode 100644
index 000000000..7db02c658
--- /dev/null
+++ b/.specs/content-columns.md
@@ -0,0 +1,153 @@
+---
+name: content-columns
+category: marketing
+structure: monolithic
+status: implemented
+spec_version: 2
+checksum: d674e4f3cf780f09fcd9b1c370e5172249697cd6df5d0c45fb1a673a1c579d44
+created: 2026-09-22
+last_updated: 2026-09-26
+---
+
+# Content Columns — Component Spec
+
+## Purpose
+
+A titled band whose body is two or three columns of short copy, drawn in one hairline `card-grid` so the columns read as a single surface. It is the plainest way to state a few parallel points under one section header.
+
+## When to use
+
+- For two or three parallel points that carry equal weight under one section heading.
+- When each point is a short title and a sentence — no icon, no media, no link.
+- As the body of a product or platform section that needs structure but not tiles.
+
+## When NOT to use
+
+- When the points are unequal and one deserves the full width → use `bento-grid`.
+- When each point needs an icon, a link or its own surface → use `topic` in a `card-grid`.
+- For measured figures → use `big-numbers`.
+- For one point paired with a picture → use `media-split`.
+
+## Related
+
+- `bento-grid` — the same collapsed-rule grid with unequal, slot-composed cells.
+- `topic` — the richer unit when a point needs an icon or a destination.
+- `section-title` — the header this band renders above its columns.
+- `big-numbers` — the numeric sibling of this grid.
+
+## Best practices
+
+- The band draws no frame of its own: the rule and the corner marks belong to the `section-module` or `frame-box` that the page assembles it into, so a band placed in a framed column never lands a second hairline on the column's own.
+
+- Keep every column to a title and one or two sentences; the grid stretches to the tallest, so one long column pads the rest.
+- Use three columns for three peers and two for a contrast; four points want `bento-grid` or a card grid instead.
+- Write the column titles in parallel grammar — they are read across, not down.
+- Leave `title` out when the band sits directly under another header; two headings in a row is one too many.
+
+## Usage
+
+```vue
+
+
+
+
+
+```
+
+## Props
+
+| Prop | Type | Default | Required | JSDoc |
+|---|---|---|---|---|
+| `items` | `ContentColumnItem[]` | `[]` | false | The columns, in reading order; each item is `{ title, description }`. |
+| `title` | `string` | `''` | false | Headline of the band, rendered as its `h2` above the columns. |
+| `description` | `string` | `''` | false | Supporting sentence under the headline. |
+| `eyebrow` | `string` | `''` | false | Short uppercase overline rendered above the headline. |
+| `columns` | `2 \| 3` | `3` | false | How many columns the grid holds from the medium breakpoint up; below it the columns stack. |
+
+## Events
+
+| _none_ | — | — |
+
+## Slots
+
+| _none_ | — | — |
+
+## States
+
+- Visual states: `default`
+- `data-columns` mirrors the `columns` prop and drives the grid from the medium breakpoint up
+- Below `md` the grid is a single column in DOM order, so the points are read in the order they are written
+- Empty: when `items` is empty the band renders no grid and no rules, so an unfilled band leaves no empty frame
+
+## Motion & Animations
+
+_none_
+
+## Tokens
+
+| Region | Token (DESIGN.md) |
+|---|---|
+| typography (column title) | `.text-heading-sm` |
+| typography (column body) | `.text-body-sm` |
+| column title text | `var(--text-default)` |
+| column body text | `var(--text-muted)` |
+| grid rules and marks | `var(--border-default)` |
+| column surface | `var(--bg-surface)` |
+| spacing (column padding) | `var(--spacing-xl)` |
+| spacing (column stack) | `var(--spacing-sm)` |
+
+## Theme gaps
+
+| Figma variable | Temporary primitive | Follow-up |
+|---|---|---|
+| _none_ | — | — |
+
+## Accessibility (WCAG 2.1 AA)
+
+- Visible focus: not applicable — the band is static copy and holds no interactive control.
+- Keyboard map: none of its own; the band contains no focusable element and is skipped by `Tab`.
+- ARIA: the band is a `` labelled by its `h2` when `title` is set; each column title is an `h3`, so the columns nest correctly under the band's heading and the document outline stays honest.
+- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons): column titles are `var(--text-default)`; bodies use `var(--text-muted)`, which clears AA on the dark canvas and is a known theme-level shortfall on the light one (see `big-numbers`).
+- `motion-reduce:transition-none motion-reduce:transform-none` — not applicable, the band is static.
+- Touch target ≥40×40 px — not applicable, no interactive target.
+
+## Stories (Storybook)
+
+- Default
+- Columns — composite story rendering both `columns` values one under the other
+
+## Constraints — DO NOT
+
+
+
+- Do not add props beyond the Props table above. If you need a prop that is not listed, emit `BLOCKED: missing prop ` and stop — do not invent.
+- Do not add events beyond the Events table above. Same rule for slots and sub-components.
+- Do not invent imports. Every `@aziontech/webkit/*` path must exist in `packages/webkit/package.json#exports`. Every relative import must resolve to a real file. Every npm package must be installed.
+- Do not use HEX/RGB/HSL colors, Tailwind palette names (e.g. `bg-blue-500`), raw typography classes (e.g. `text-sm`), `any`, `@ts-ignore`, or `class` inside `defineProps`.
+- Do not install or import positioning/animation libraries (`@floating-ui/*`, `popper.js`, `tippy.js`, `gsap`, `framer-motion`, `motion`, `@vueuse/motion`, `@formkit/auto-animate`, drag-drop runtimes, scroll virtualization libs). Use CSS + Vue primitives (``, ``). See `.claude/rules/dependencies.md`.
+- Do not improvise animations. Every `animate-*` / `transition-*` class must come from `packages/theme/src/tokens/semantic/animations.js`; every motion-bearing class pairs with `motion-reduce:*` on the same class string; no component-local `@keyframes`.
+- Do not create class presets in JavaScript (`const kindClasses = {...}`, `const sharedClasses = [...]`, `const sizeClasses = {...}`, `const rootClasses = computed(...)`). Variants live on `data-*` attributes consumed by Tailwind `data-[attr=value]:`. All utilities live inline on the root element's `class` attribute. No `