diff --git a/docs/ADS.md b/docs/ADS.md
index 37f7d60..23310b1 100644
--- a/docs/ADS.md
+++ b/docs/ADS.md
@@ -139,6 +139,111 @@ ASG керує цими SDK runtime-no — ми бачимо тільки iframe
⚠️ **Не забути `layout.etlua` cooldown regex** — окрема hardcoded reference на popunder spot ID (для localStorage `asgsl` cookie matching).
+### Native 1thumb_a..f — single-banner-per-thumb pattern
+
+`views/modules/banners/native_allpg_1thumb_{a,b,c,d,e,f}.etlua` — **6 окремих banner модулів**, кожен:
+- ОДИН `
` (унікальний DOM ID `#thmb1` … `#thmb6`)
+- ОДИН spot URL `//a5.g--o.info/api/spots/
?p=1`
+- script `tb.load_frame_baner_v2(spotURL, "#thmb", ...)` injects iframe всередину
+
+**Призначення:** заміняти thumbnail у grid-листингах (`cat_list_thumb`, `video.etlua`, `related_video`, `recommended_video`) на native banner imp що виглядає як thumb.
+
+**Render умова:** if `i == 8 and not isMobile()` (desktop) **OR** `i == 5 and isMobile()` (mobile) — позиція у grid де banner injects.
+
+⚠️ **Унікальність DOM ID per page:** один render = один module = один `#thmbN`. Якщо рендериш 1thumb_a у двох різних модулях (наприклад related_video AND recommended_video) на одній сторінці — отримаєш **DUPLICATE `#thmb1`** (HTML5 violation, ad iframe injects only into first match). Fix: використовуй РІЗНІ модулі (`1thumb_a` + `1thumb_b`) у різних блоках.
+
+### Per-site spot inventory
+
+#### 8148 (xn--3dsq7teoyo9d.com — pilot site)
+
+Канонічний reference. Spot IDs у git history; перелік типів — у matrix вище.
+
+#### 8161 (adultmovz.com)
+
+Migrated 2026-05-03 (videojs4 → PlayerJS+ad-bundle). URL pattern `/v-arch/`.
+
+| Spot ID | Тип | DOM target | File |
+|---------|-----|------------|------|
+| **514125** | popunder | — | `static/js/ad-config.js` + cooldown regex у `layout.etlua` |
+| **514120** | VAST in-video | `pjs_play_btn` click | `static/js/ad-config.js` |
+| 514116 | desktop footer A | `#da_a` | `modules/banners/footer_descktop_adspy.etlua` |
+| 514117 | desktop footer B | `#da_b` | same |
+| 514118 | desktop footer C | `#da_c` | same |
+| 514119 | desktop footer D | `#da_d` | same |
+| 514123 | mobile header | `#hdm` | `modules/banners/header_mobile_adspy.etlua` |
+| 514124 | mobile footer | `#ftm` | `modules/banners/footer_mobile_adspy.etlua` |
+| 514138 | mobile middle | `#mdm` | `modules/banners/middle_mobile_adspy.etlua` |
+| 514121 | desktop sidebar A | `#sdd_a` | `modules/banners/embed_sidebar_adspy.etlua` |
+| 514122 | desktop sidebar B | `#sdd_b` | same |
+| 514139 | mobile sidebar A | `#dsd_a` | `modules/banners/embed_mobile_sidebar_adspy.etlua` |
+| 514140 | mobile sidebar B | `#dsd_b` | same |
+| 514295 | native desktop allpg | `#tbn1` | `modules/banners/native_allpg_desktop_adspy.etlua` |
+| 514297 | native mobile allpg | `#tbn2` | `modules/banners/native_allpg_mobile_adspy.etlua` |
+| 514296 | native embed | `#tbn3` | `modules/banners/native_embed_adspy.etlua` |
+| 514371 | native 1thumb A | `#thmb1` | `modules/banners/native_allpg_1thumb_a.etlua` (use: related_video, cat_list_thumb, video) |
+| 514372 | native 1thumb B | `#thmb2` | `1thumb_b.etlua` (use: recommended_video, cat_list_thumb, video) |
+| 514373 | native 1thumb C | `#thmb3` | `1thumb_c.etlua` (cat_list_thumb, video) |
+| 514374 | native 1thumb D | `#thmb4` | `1thumb_d.etlua` (cat_list_thumb, video) |
+| 514375 | native 1thumb E | `#thmb5` | `1thumb_e.etlua` (cat_list_thumb, video) |
+| 514376 | native 1thumb F | `#thmb6` | `1thumb_f.etlua` (cat_list_thumb, video) |
+
+## Migration checklist — legacy (videojs4) → modern (PlayerJS + ad-bundle)
+
+Контрольні чекпоінти при міграції наступного site (template після 8148/8161 досвіду). Кожен ✓ — окремий atomic commit.
+
+### Pre-flight
+- [ ] **Identify URL pattern.** `/v/` (default), `/v-arch/`, `/video/`, etc. Це йде у `ad-bootstrap.js` path checks (2 occurrences) і у тестових URL-ах.
+- [ ] **Get spot IDs from ASG admin.** Мінімум 16 spots (popunder + VAST + 4 desktop footer + 1 mobile header + 1 mobile footer + 1 mobile middle + 2 desktop sidebar + 2 mobile sidebar + 3 native + опціонально 6 1thumb_a-f). Записати mapping.
+- [ ] **Verify ASG spot config:** **frequency capping ENABLED** на popunder + VAST (інакше cooldown поломається — див. `## ⚠️ ASG spot config gotcha` вище).
+
+### Banner modules
+- [ ] **Replace banner spot IDs** у всіх `views/modules/banners/*adspy*.etlua` + `1thumb_*.etlua` (per inventory). Sed-batch friendly.
+- [ ] **Verify zero stale IDs** після replace: `grep -r '' views/` повинен returnати empty (test/comments OK).
+
+### JS bundle (port from 8148)
+- [ ] **Copy 5 source files** → `views/static/js/`: `ad-config.js`, `ad-bootstrap.js`, `ad-core.js`, `ad-mute.js`, `vast-preroll.js` + `build-ad-bundle.sh`.
+- [ ] **Edit `ad-config.js`:** set `popunder.spot`, `vast.spot`, `_adConfigBuildId` (унікальний marker для cache-bust verify).
+- [ ] **Edit `ad-bootstrap.js`:** додати site URL pattern до **обох** path checks (HUD interval + `_adCtx` init), напр. `||location.pathname.indexOf('/v-arch/')===0`.
+- [ ] **Build bundle:** `bash views/static/js/build-ad-bundle.sh` (concat + terser).
+
+### layout.etlua
+- [ ] **Add `class="no-js"` на ``** + inline script `` swap до `.js` (для iOS Safari `.js .vi-limiter video::-webkit-media-controls{display:none}` selector).
+- [ ] **Add ``.**
+- [ ] **Add `_adSel` inline config** у `mysettings.location_css == "id"` block з selector mapping (native classes — `vi-limiter` у 8161, `vdo-blk-lmtr` у 8148).
+- [ ] **Tabunder pre-detect script** з popunder spot ID regex `(?:^|\|)=` (синхронізовано з ad-config!).
+- [ ] **``** з cache-bust md5.
+- [ ] **Inline `lazyLoadFunc` definition** (з `views/static/js/lazysizes.min.js` content) ПІСЛЯ inline lazysizes core block — інакше `timeline-pjs.min.js` кине `ReferenceError: lazyLoadFunc is not defined` (8161 fix 2026-05-04).
+- [ ] **Body inline scripts** (port 8148 lines 184-434): `_initPjs()`, `_revealPjs()`, `_playAt()`, click handler на `.` capture phase, VAST overlay click forwarder.
+- [ ] **Guard counter calls:** `tb.start_events_v2(() => { if(typeof c==="function") c(<%-video.video_id%>,0,"click",0); }, ...)` — `c()` defined у async-loaded `counters.v2.min.js`, без guard race-error (`c is not defined`).
+
+### id_index.etlua
+- [ ] **Replace videojs4 DOM** з PlayerJS structure: `` + `