diff --git a/README.md b/README.md
index 35d80ea..f061a9b 100644
--- a/README.md
+++ b/README.md
@@ -92,7 +92,7 @@ near-misses to reuse instead:
Live sample outputs, committed from that same solution:
**[HTML report](https://htmlpreview.github.io/?https://github.com/Karzone/TestAtlas/blob/main/docs/sample-report.html)** (features, scenarios, bindings, class kinds, endpoints) ·
-**[dependency map](https://htmlpreview.github.io/?https://github.com/Karzone/TestAtlas/blob/main/docs/sample-map.html)** (the eight projects and their edges).
+**[dependency map](https://htmlpreview.github.io/?https://github.com/Karzone/TestAtlas/blob/main/docs/sample-map.html)** (the eight projects in layers — click one to see what it uses, what uses it, and which tests a change can reach).
(GitHub serves `.html` as source, so the links route through htmlpreview.github.io — or download from [`docs/`](docs/) and open locally.)
## What you get
@@ -108,7 +108,7 @@ them — then turns that map into answers:
| 💥 | **Impact** | Blast radius — the scenarios affected by changing a class, method, step, or endpoint |
| 🔍 | **Search** | FTS5 over step definitions + scenarios — *"does a step for this already exist?"* |
| 🔌 | **MCP** | All of it served to an AI agent over stdio — precise answers in a few hundred tokens, no context stuffing |
-| 📊 | **Report & map** | Self-contained HTML drill-down of the whole map + project-dependency graph |
+| 📊 | **Report & map** | Self-contained HTML drill-down of the whole map + a layered, searchable project-dependency graph |
| 📈 | **Stats** | Entity counts, class-kind breakdown, binding coverage, diagnostics |
All of it **offline, deterministic, and reproducible** — same input, same map, every time.
diff --git a/docs/sample-map.html b/docs/sample-map.html
index 804b825..91c63e6 100644
--- a/docs/sample-map.html
+++ b/docs/sample-map.html
@@ -1,199 +1,700 @@
-
TestAtlas map — SampleShop.sln
TestAtlasproject map
SampleShop.sln
8 projects · 11 dependencies · 4 build-only references (dashed) · an arrow A→B means A depends on B (binds to / uses / inherits something in B; dashed = .csproj reference only)
BDD testsshared libraryunit testsotherclick a project to pin its dependencies · drag to pan · scroll to zoom
TestAtlasProject map
SampleShop.sln
8projects
11code dependencies
1build-only reference
/
Entry points3
Shared code4
Foundation1
Api.Cart
Library
Api.Catalog
Library · 2 endpoints
Api.Identity
Library · 1 endpoint
Core
Library
Tests.Api
BDD tests
Tests.E2E
BDD tests
Tests.Ui
BDD tests
Ui.Pages
Library
Arrows point to what a project depends on
Code dependencyBuild-only referenceWhat it usesWhat uses it
/ search · F fit · Arrow keys move · T scope · Esc clear
\ No newline at end of file
+ function end(){ if(down&&down.moved){grabbing=false; view.classList.remove('grabbing');} down=null; }
+ view.addEventListener('pointerup',end); view.addEventListener('pointercancel',end);
+ // A background click (not a card or edge, not the tail of a drag) clears the pin or selection.
+ view.addEventListener('click',function(){ if(consumeDrag()) return; if(panelOpen()) unpin(); });
+ view.addEventListener('wheel',function(e){
+ e.preventDefault();
+ var u=e.deltaMode===1?16:e.deltaMode===2?400:1;
+ if(!e.ctrlKey&&Math.abs(e.deltaX)>Math.abs(e.deltaY)){
+ userMoved=true; cancelAnimationFrame(raf); setView({tx:S.tx-e.deltaX*u,ty:S.ty,k:S.k}); return;
+ }
+ zoomAt(e.clientX,e.clientY,Math.exp(-e.deltaY*u*(e.ctrlKey?0.01:0.0015)));
+ },{passive:false});
+
+ document.getElementById('zout').addEventListener('click',function(){zoomBy(1/1.2);});
+ document.getElementById('zin').addEventListener('click',function(){zoomBy(1.2);});
+ zpct.addEventListener('click',function(){zoomBy(1/S.k);});
+ document.getElementById('zfit').addEventListener('click',function(){userMoved=false; fitView(true);});
+
+ // ---- search ----
+ function computeResults(){
+ var q=S.q.trim().toLowerCase(), res=ids.map(function(id){return N[id];});
+ if(q){
+ var sw=function(n){return n.short.toLowerCase().indexOf(q)===0?1:0;};
+ res=res.filter(function(n){return n.name.toLowerCase().indexOf(q)>=0||kl(n.kind).toLowerCase().indexOf(q)>=0;})
+ .sort(function(a,b){return sw(b)-sw(a)||a.short.localeCompare(b.short);});
+ } else res.sort(function(a,b){return a.col-b.col||a.row-b.row;});
+ return res.slice(0,8);
+ }
+ function renderSearch(){
+ qBox.classList.toggle('has',S.searchFocused||!!S.q);
+ qIn.setAttribute('aria-expanded',S.searchFocused?'true':'false');
+ if(!S.searchFocused){qRes.hidden=true; qIn.removeAttribute('aria-activedescendant'); return;}
+ results=computeResults();
+ var idx=Math.max(0,Math.min(S.searchIdx,results.length-1));
+ qRes.innerHTML=results.length?results.map(function(n,i){
+ return '