diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md
index a8b3f8f..ed11049 100644
--- a/CONTRIBUTE.md
+++ b/CONTRIBUTE.md
@@ -33,6 +33,8 @@ cd website && npm run fetch:videos
This updates `website/lib/video-titles.json` (commit it alongside the markdown
change).
+**Images:** Put the file in `website/public/images/` and reference it as ``. The site prepends the deployment base path when rendering.
+
**Relationships:** Define in `documents/relationships.mmd` using Mermaid graph syntax:
```mermaid
patterns/your-pattern -->|solves| obstacles/some-obstacle
diff --git a/documents/patterns/surface-change-attractors.md b/documents/patterns/surface-change-attractors.md
new file mode 100644
index 0000000..05160d7
--- /dev/null
+++ b/documents/patterns/surface-change-attractors.md
@@ -0,0 +1,35 @@
+---
+authors: [ivett_ordog]
+alternative_titles: ["Hotspot Analysis"]
+---
+
+# Surface Change Attractors
+
+## Problem
+Architectural problems hide as churn. Some files attract every change; some always change together even though the module structure claims they're independent. Humans normalize this friction. Agents pay for it too: hot files get re-read into context constantly, and edits concentrate exactly where the conflicts and bugs live. The structure looks fine; only the history shows the problem.
+
+## Pattern
+Mine version control for behavioral signals (Adam Tornhill-style analysis):
+
+- **Hotspots**: change frequency × complexity — files that are both churned and complicated
+- **Change coupling**: files that repeatedly change in the same commits despite no visible dependency
+
+Hand the findings to the agent as design constraints:
+
+- "These five files change together in most commits — propose a redesign that lets them change independently"
+- "This file is touched by every feature — split it along its reasons to change"
+
+Refactor incrementally toward the proposed design.
+
+The metrics nominate candidates, they never decide. Churn says where change lands, the code says why — read every candidate before acting on it.
+
+Canary in the Code Mine reads the agent's live struggle; Surface Change Attractors finds the same signal in your history, before the struggle.
+
+An open-source implementation is available at https://github.com/devill/ivetts-skills#hotspot-rec
+
+
+
+*Circles are files, sized by lines and colored by commits; outlined circles are packages. Orange dashed lines join files that change together across a package boundary — co-change the architecture says should not happen.*
+
+## Example
+A script over `git log` counts per-file commit frequency and co-change pairs (code-maat and CodeScene do this out of the box — or Offload Deterministic: have the agent write the script). The top attractor is a 900-line "service" touched in 70% of commits, co-changing with a validator and a serializer in two other modules. Given those constraints, the agent proposes moving validation and serialization behind the service's interface. After the refactor, features stop fanning out across three modules.
diff --git a/documents/relationships.mmd b/documents/relationships.mmd
index b4046f6..38d038a 100644
--- a/documents/relationships.mmd
+++ b/documents/relationships.mmd
@@ -10,6 +10,9 @@ graph LR
patterns/active-partner -->|solves| obstacles/obedient-contractor
patterns/canary-in-the-code-mine -->|solves| obstacles/degrades-under-complexity
patterns/canary-in-the-code-mine -->|solves| obstacles/context-rot
+ patterns/surface-change-attractors -->|solves| obstacles/degrades-under-complexity
+ patterns/surface-change-attractors <-->|similar| patterns/canary-in-the-code-mine
+ patterns/surface-change-attractors -->|uses| patterns/offload-deterministic
patterns/chain-of-small-steps -->|solves| obstacles/degrades-under-complexity
patterns/chain-of-small-steps -->|solves| obstacles/limited-focus
patterns/check-alignment -->|solves| anti-patterns/silent-misalignment
diff --git a/website/CLAUDE.md b/website/CLAUDE.md
index 9d08a8c..dbbf9a7 100644
--- a/website/CLAUDE.md
+++ b/website/CLAUDE.md
@@ -47,6 +47,8 @@ npx playwright test path/to/test.spec.ts
**Important**: The first H1 in markdown files is extracted as the page title and removed from the rendered content to maintain semantic HTML (only one H1 per page).
+**Images in documents** (agent decision): document images live in `public/images/` and are referenced from markdown as `/images/{file}`. Every `ReactMarkdown` call passes `markdownComponents` from `app/components/markdownComponents.tsx`, which renders images through `next/image` with the deployment `basePath` prepended — a bare `` would 404 on GitHub Pages. Add the override to any new `ReactMarkdown` call site.
+
### Category Configuration System
**Centralized in** `app/lib/category-config.ts`:
diff --git a/website/app/[category]/[slug]/page.tsx b/website/app/[category]/[slug]/page.tsx
index ae345fd..702da51 100644
--- a/website/app/[category]/[slug]/page.tsx
+++ b/website/app/[category]/[slug]/page.tsx
@@ -9,6 +9,7 @@ import { PatternCategory } from "@/lib/types";
import Authors from "@/app/components/Authors";
import RelatedLinks from "@/app/components/RelatedLinks";
import VideoThumbnail from "@/app/components/VideoThumbnail";
+import { markdownComponents } from "@/app/components/markdownComponents";
import styles from "../../pattern-detail.module.css";
interface PatternPageProps {
@@ -155,7 +156,7 @@ export default async function PatternPage({ params }: PatternPageProps) {