<!-- source: openspec/changes/apply-connected-stack-coordinate-grid/design.md -->

# Design — apply CAD coordinate-grid pattern to the connections section

## Decision summary

| Decision | Choice |
|---|---|
| Host section | `connected-stack` (id `connections`), paint-map row 06 |
| Pattern | `CAD/08` — `bg-pattern-cad`, coordinate grid, 60px pitch, crosshair nodes |
| Tone | `text-blueprint` (structural; no accent — accent budget stays spent on `closing-cta`) |
| Intensity | `opacity-70` (the `normal` bucket — same weight the shipped P4 hatch reads at) |
| Zone | section's empty **bottom padding**, the `D-DS-8` bottom-strip idiom |
| Fade | `pattern-fade-top`, applied to a **wrapper**, not the CAD element |
| Strip height | `h-16 md:h-32` — matches the section's `py-16 md:py-32` bottom padding exactly so the strip never overlaps the CTA |
| Budget | total-per-page cap raised **2 → 3**; spacing rule (≥1 bare section between placements) becomes the governing constraint |
| A11y | `aria-hidden` on the strip wrapper; `pointer-events-none` |

## Repo reconciliation — verify before editing

The placement assumptions below were read from `main`; confirm each still holds
before writing (mirrors the discipline of the globe bundle's corrections table).

| Assumption | Where | Confirm |
|---|---|---|
| Section file + className `md:border-t md:border-grid py-16 md:py-32` | `apps/web/src/components/home/connected-stack.tsx` | open the file; the `<section id="connections">` opening tag |
| `bg-pattern-cad` and `pattern-fade-top` exist | `packages/ui/src/styles/patterns.css` | grep both class names |
| `text-blueprint` resolves (token) | `packages/ui/src/styles/globals.css` | grep `--color-blueprint` |
| Paint-map lives here, row 06 = `connected-stack` bare | `openspec/current/home/blocks.md § Background patterns paint-map` | grep `connected-stack` in that section |
| Density-budget table location | `openspec/current/design-components.md § Background patterns › Density budget` | **may still be inbound via `document-background-patterns-in-current`** — if `design-components.md` is not yet in `current/`, apply the budget delta to whichever file currently holds the budget table, and note it in the PR body |
| `D-DS-8` decision record | `openspec/decisions.md` | reference only; this change adds a sibling record (see below) |

## Placement spec

`connected-stack`'s content order is: `SectionHead` → tile grid → mono footnote
→ CTA line. The CTA is the last text. Below it sits the section's `py-16
md:py-32` bottom padding — a clean text-free band. The pattern lives there.

**Edit 1 — make the section a positioning context.** Add `relative` as the
first class on the existing `<section>`:

```tsx
// before
<section id="connections" aria-labelledby="connections-heading"
  className="md:border-t md:border-grid py-16 md:py-32">

// after
<section id="connections" aria-labelledby="connections-heading"
  className="relative md:border-t md:border-grid py-16 md:py-32">
```

**Edit 2 — append the strip as the last child of `<section>`,** after the CTA
`<p>` and before `</section>`:

```tsx
{/* P5 · RAP-1265 — CAD coordinate-grid tile in the section's bottom
    padding (text-free zone, D-DS-8). The page's third placement; the
    coordinate grid reads as the registration field beneath "the
    connected stack." bg-pattern-cad owns mask-image for its crosshair
    geometry, so the upward fade is applied to the WRAPPER — mask-image
    composes across nesting — NOT to the tile element. That keeps this
    clear of the same-element `pattern-fade-* + bg-pattern-cad`
    anti-pattern (silent mask override). Height tracks the section's
    bottom padding so the strip never overlaps the CTA. */}
<div
  aria-hidden
  className="pointer-events-none absolute inset-x-0 bottom-0 h-16 md:h-32 pattern-fade-top"
>
  <div className="absolute inset-0 bg-pattern-cad text-blueprint opacity-70" />
</div>
```

This mirrors the `for-legacy` P3 idiom (`apps/web/src/components/home/for-legacy.tsx`)
one-for-one, except the inner tile + wrapper-fade split that CAD requires.

## The CAD fade — why it nests

`bg-pattern-cad` paints `currentColor` through an inline-SVG **alpha mask**
that carries the grid + crosshair geometry. The `pattern-fade-*` utilities are
*also* `mask-image`. Put both on one element and the fade silently overwrites
the crosshair mask — the documented anti-pattern, and why the doc says "to
soften CAD, drop opacity."

Dropping opacity alone leaves a **hard top edge** on the strip — it reads as a
panel, not a texture dissolving into the section. Both shipped placements fade
upward; a hard-edged third placement would break the idiom.

The resolution: **two elements.** The CAD tile keeps its own mask. The fade
mask goes on a parent wrapper. `mask-image` applies to an element's entire
painted subtree, so the wrapper's upward-fade alpha mask is composited over the
already-masked CAD child — the two masks multiply in the result. Dense
crosshairs at the section's bottom edge, dissolving up toward the CTA, with no
visible gradient *fill* anywhere (the fade is an alpha mask, blessed under
`D-DS-8` exactly as the existing `pattern-fade-*` utilities are).

**Do not** reproduce the prototype's fade. The exploration
(`Connections — Pattern Study`, variant "03 · CAD") softened the top with a
`background: linear-gradient(... var(--color-canvas))` overlay. That is a real
gradient *fill* and violates the no-gradient hard constraint. The prototype was
a look study; production uses the nested mask.

Effective mask-mode: the shipped `for-legacy` / `closing-cta` fades prove the
target engines treat `pattern-fade-*` gradient masks as **alpha** (`#000` =
opaque = shown, `transparent` = hidden → dense bottom, clear top). The nested
CAD case relies on the same engines and the same utility; `verification.md`
includes a visual gate that fails loudly if the nesting does not composite.

## Intensity

`opacity-70` (`normal`). The shipped P4 hatch reads at `normal`; matching it
keeps the two tile placements at one weight. CAD's 60px crosshair field is
visually sparser than hatch's 14px lines, so `normal` does not over-read. This
also matches the density the approved prototype showed (its 0.85 + overlay
rounds to the `normal` bucket once the fill-fade is replaced by the mask-fade).
`bold` (1.0) is the untested figure-vs-ground bucket — not used.

## Density-budget governance (the load-bearing decision)

Current page budget (`design-components.md § Density budget`):

| Surface | Limit | Spent |
|---|---|---|
| Composition patterns | 1 / page | 1 — `for-legacy` Modulor |
| Tile patterns | 1 / section | 0 on `connected-stack` |
| **Patterns total** | **2 / page** | **2** → this change makes 3 |
| Accent-tone patterns | 1 / page | 1 — `closing-cta` |

CAD is a **tile** (`CAD/08`, tile family) in `blueprint` tone. It does not
touch the composition, tile-per-section, or accent sub-budgets. It breaches
only **total-per-page**.

**Resolution: raise total-per-page 2 → 3.** Rationale: the `2/page` cap was a
proxy for the real failure mode — *patterns clustering or competing until they
read as decoration*. The catalog already encodes the true constraint as an
anti-pattern: **placements must be separated by ≥1 bare section**. The three
placements satisfy it with room to spare —

```
06 connected-stack  ·······  10 for-legacy  ···  13 closing-cta
   (mechanism)                 (segment reset)      (terminal close)
```

— three structurally distinct moments, each ≥1 bare section from the next,
across a 13-section page. That reads as punctuation, not decoration. The change
promotes the spacing rule from anti-pattern note to the governing budget
constraint and lifts the count to 3.

**Alternative considered — displacement.** Apply CAD *and* drop `for-legacy`
P3 or `closing-cta` P4 to stay at 2. Rejected: both existing placements sit on
the page's quietest moments (mid-page reset, terminal close) and were ratified
on review (RAP-1035). Removing either to spend the budget elsewhere is a net
loss; the page can carry a well-spaced third.

This decision is recorded as **D-DS-9** (sibling to D-DS-8). `tasks.md`
includes adding the short-form record to `openspec/decisions.md`.

## Grid-atop-grid — why the prior verdict is overridden

Paint-map row 06 reads *bare · already literal grid*: "Section **is** a grid …
a `graph` or `dots` tile here is a grid pattern atop a literal grid." That
verdict assumed a tile rendered **under or within** the `grid-cols-3` card
lattice, doubling it.

This placement is different on two axes:

1. **Location.** The CAD field is in the **empty bottom padding**, spatially
   below and apart from the cards — not behind them. There is no card lattice
   in the strip's bounds to double.
2. **Pitch + reading.** CAD's 60px coordinate pitch with weighted crosshair
   nodes is coarser and semantically distinct from the card grid; it reads as
   the **registration field** the stack is plotted onto, not a second content
   grid. The "stack = coordinate system" metaphor is the point, not an
   accident.

The paint-map row is updated to record this reasoning (see
`deltas/home-blocks-paint-map.md`), so the override is argued, not silent.

## Responsive / mobile

The strip height tracks the section's bottom padding at each breakpoint:
`h-16` (64px) under `py-16`, `h-32` (128px) under `md:py-32`. The strip can
therefore never overlap the CTA text at any width — keeping the pattern off
text, as `D-DS-8` requires. CAD is pure CSS (one masked `<div>`, no JS, no
network, no RAF), so there is no perf reason to gate it off mobile; it ships at
all breakpoints. On a 64px mobile strip the 60px grid shows ~one crosshair
row — quiet, intentional.

## Accessibility

The strip wrapper carries `aria-hidden` and `pointer-events-none`; it is
decorative and announces nothing. No focusable content, no contrast obligation
(it sits in padding, never behind text). The section's existing
`aria-labelledby` is untouched.

## Risks / limitations

- **Nested-mask compositing.** If a target engine fails to composite the two
  `mask-image` layers, the fade or the crosshairs could drop. Mitigated: same
  engines + same utility as the shipped fades; visual gate in `verification.md`.
- **Budget precedent.** Raising the cap to 3 invites a 4th proposal later. The
  spacing rule (≥1 bare section) is the guard; a 4th placement on a 13-section
  page would crowd it and should be rejected on that basis. Note in the PR body.
- **Light-only.** `bg-pattern-cad` already carries `.dark` token reactivity, so
  this is forward-safe, but no dark surface is exercised on web today.
