28 KiB
tubev — Ads Integration
Реклама через adspyglass.com. Основне джерело монетизації — балансуємо CTR vs UX/PSI.
Status
🟢 Working knowledge — Skeleton наповнюємо.
⚙️ Automated regression test — ad-test
Швидкий smoke перевірити що ad flow не поламано після зміни.
# default — 3 URLs з 8148 (швидкий smoke)
ad-test
# конкретні URLs
ad-test "https://www.xn--3dsq7teoyo9d.com/v/box/123/foo" \
"https://t1.atube.sex/v/box/456/bar"
# з screenshots + JSON report
ad-test --screenshot --json "https://..." "https://..."
Що перевіряє per URL:
- ✓ ad-bundle loaded (
window._adConfigset, spot IDs читаються) - ✓ popunder SDK loads
- ✓
/api/users/<vast_spot>повертає<Ad>(не empty no-fill) - ✓ mode = "vast" після popunder cooldown
- ✓
.asg-containerinjects на clickpjs_play_btn(правильнийdata-spot-id) - ✓ console clean (no
[ASGB LOADER]errors, nopageerror)
Exit code: 0 = всі pass, 1 = ≥1 fail. Можна chain у CI / git hook.
Реалізація:
- Wrapper:
/home/w4/bin/ad-test - Script:
/home/w4/playwright-tests/ad-regression.mjs(Playwright + Chromium headless) - Repo copy (versioned):
scripts/ad-regression.mjs+scripts/ad-test.sh
Обмеження headless:
- Real video playback не verifies (autoplay restrictions, VPAID не fully supported у chromium-headless-shell)
- Network-level VAST chain validation (через
<Ad>/<InLine>/<Wrapper>детекцію) cover 95% regressions без візуальної перевірки - Якщо треба видеоплей —
ad-test --full(full chromium, але VPAID все ще incomplete) - Альтернатива для full visual: запусти у real browser з
?debug=1+ дивись HUD
Architecture
Mirrors (зеркала)
Реклама вантажиться через зеркала-домени, не прямо adspyglass.com:
a5.g--o.info— ad orchestration (preconnect target у modern setup)surstrom.com— inline ad-spot loader (legacy pattern)agego.com— verifycdn (verifycdn.agego.com/v1/verify.js)g--o.info— base domain- (інші додаються на потребу)
Чому зеркала: anti-block — adblockers баняють відомі ad-домени. Зеркала маскують та ротуються.
Mirror lifecycle:
- Зеркала періодично баняться (adblock list updates)
- Заміна — mass-operation через Адміна (одразу на всі сайти)
- Ми не керуємо вибором зеркал — це адмінська функція
Three architecture patterns coexist (audit 2026-05-02)
Site-level audit показав 3 patterns — не uniform:
| Pattern | Sites | Як виглядає | Примітка |
|---|---|---|---|
| A) Modern bundle | 1 (тільки 8148) | <script src="/static/js/ad-bundle.min.js?v=<md5>" defer> + views/modules/banners/*adspy*.etlua partials |
Canonical — є build pipeline, source JS у views/static/js/ |
| B) Modern partials | ~23 | preconnect → a5.g--o.info + adspy .etlua partials, але без ad-bundle.min.js |
Modules orchestrate, no central bundle |
| C) Legacy inline | 31 (8112-style) | Inline <script async src="//surstrom.com/<random>.js"> + iframes до surstrom.com/api/spots/<id> + tb.load_frame_baner_v2() API |
Pre-modular epoch |
77 із 94 сайтів мають adspy banner modules у views/modules/banners/*adspy*.etlua. Решта (17) — legacy без banner partials або special structure.
Build pipeline (тільки 8148)
/home/nosfortube/frontend_8148/views/static/js/build-ad-bundle.sh концатує+minify-їть 5 source JS-файлів у ad-bundle.min.js:
ad-config.js— config / placement IDs / mirror domainsad-core.js— ядро ad-orchestrationad-mute.js— mute / autoplay controlvast-preroll.js— pre-roll реклама перед videoad-bootstrap.js— entry-point / init
Source files: усі у /home/nosfortube/frontend_8148/views/static/js/ (унікально для 8148).
Tool: terser (concat + minify). Triggered by ~/git-save-all.sh коли source новіший за bundle.
Cache-bust: md5 → ?v=... у layout.etlua (детально у DEPLOY.md).
Banner partial loader — tb.load_frame_baner_v2
Source: views/static/js/lib/common/js/tbanner.etlua (shared lib з кешуючою етлуа-обгорткою) + tbanner_min.etlua (minified inline). Підключається у layout.etlua через <% render("static.js.lib.common.js.tbanner_min") %>.
Сигнатура: tb.load_frame_baner_v2(spot_url, dom_target, timeouts, attrs [, callback])
spot_url—//a5.g--o.info/api/spots/<spot_id>?p=1dom_target—#selectorкуди append iframetimeouts—{event_min_timeout, event_max_timeout}msattrs—{height, width, ...}iframe attributes / stylecallback— викликається після append (для native — інсталяція postMessage listener-у)
Lazy-load logic:
- Чекає user event (
scroll,mousemove,touchstart,resize,mouseenter,click) АБОevent_min_timeout(раніше) - Якщо tab hidden → відкладає до
visibilitychange event_max_timeout— fallback hard limit- Counter per banner — окрема черга для кожного instance
- iframe attrs default:
sandbox="allow-scripts allow-popups allow-forms allow-same-origin",loading="lazy",class="na"
Native banners — auto-height через postMessage
Native (tbn1, tbn2, tbn3) — adaptive ads з невідомою висотою. Pattern:
- На стороні ASG користувач прописує код банера (TrafficStars або ExoClick) + кастом-CSS для responsive grid + кастом-JS який postMessage-ить розмір.
- На стороні сервера ASG обертає у iframe (через
/api/spots/<id>endpoint). - Frontend ловить
window.messageevent від iframe → парсить JSON{url, height}→ встановлюєheightattribute на iframe зtransition: all .3s.
Network providers за ASG (приклад інтеграції на adспy admin side):
- TrafficStars —
cdn.tsyndicate.com/sdk/v1/n.js+NativeAd({element_id, spot, type: "label-under", cols:4, rows:1, ...}) - ExoClick —
a.magsrv.com/ad-provider.js+<ins class="eas..." data-zoneid="..." data-keywords="%KW%" data-sub="%SUB1%"></ins>+(AdProvider).push({"serve":{}})
Обидва вкладають кастомний <style> з 4-col responsive grid (≥739px: 4 cols, 450-738: 2, <450: 1). Плюс postMessage скрипт що шле parent розмір на load/resize/mutation.
ASG керує цими SDK runtime-no — ми бачимо тільки iframe.
Spot ID location matrix (8148 — 16 spots)
| Тип | Файл | Spot ID location |
|---|---|---|
| popunder | views/static/js/ad-config.js |
popunder.spot |
| VAST/in-video | views/static/js/ad-config.js |
vast.spot |
| popunder cooldown matcher | views/layout.etlua |
inline regex `(?:^ |
| desktop footer ×4 | views/modules/banners/footer_descktop_adspy.etlua |
hardcoded URL spots/<id>?p=1 |
| mobile footer/header/middle | views/modules/banners/{footer,header,middle}_mobile_adspy.etlua |
same |
| desktop sidebar ×2 | views/modules/banners/embed_sidebar_adspy.etlua |
same |
| mobile sidebar ×2 | views/modules/banners/embed_mobile_sidebar_adspy.etlua |
same |
| native (3 partials) | views/modules/banners/native_*adspy.etlua |
same |
При зміні spot IDs (rotation / re-creation): оновити ВСІ перелічені місця, потім bash /home/w4/git-save-all.sh "msg" — auto-rebuild bundle + cache-bust.
⚠️ Не забути 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 модулів, кожен:
- ОДИН
<div id="thmb<N>">(унікальний DOM ID#thmb1…#thmb6) - ОДИН spot URL
//a5.g--o.info/api/spots/<id>?p=1 - script
tb.load_frame_baner_v2(spotURL, "#thmb<N>", ...)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.jspath 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 '<old_id>' 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: setpopunder.spot,vast.spot,_adConfigBuildId(унікальний marker для cache-bust verify). - Edit
ad-bootstrap.js: додати site URL pattern до обох path checks (HUD interval +_adCtxinit), напр.||location.pathname.indexOf('/v-arch/')===0. - Build bundle:
bash views/static/js/build-ad-bundle.sh(concat + terser).
layout.etlua
- Add
class="no-js"на<html>+ inline script<head>swap до.js(для iOS Safari.js .vi-limiter video::-webkit-media-controls{display:none}selector). - Add
<link rel="preconnect" href="https://a5.g--o.info" crossorigin>. - Add
_adSelinline 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
(?:^|\|)<popunder_id>=(синхронізовано з ad-config!). <script src="/static/js/ad-bundle.min.js?v=<md5>" defer></script>з cache-bust md5.- Inline
lazyLoadFuncdefinition (зviews/static/js/lazysizes.min.jscontent) ПІСЛЯ 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 на.<lmtr_class>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-loadedcounters.v2.min.js, без guard race-error (c is not defined).
id_index.etlua
- Replace videojs4 DOM з PlayerJS structure:
<div class="<lmtr_class> is-loading"><div class="<vdo_class> js-cleanVideo" id="pjs_container">+<video>+<div id="pjs_poster">+<img id="pjs_poster_img">+<div id="pjs_play_btn">SVG +<div id="pjs_loader">SVG. - Use site-NATIVE classes (не copy 8148
vdo-blk-lmtr/vdo-blk-vdo). Map до існуючих сайту: 8161 →vi-limiter/vi-player. Foreign classes створюють footprint і CSS дублі. - Inline styles target NATIVE classes теж (
<style>.vi-limiter ...</style>). Без дзеркал sed. - Timeline click handler:
<a onClick="_playAt(_SECOND_)">(НЕ legacyplayer.setTime). - Poster touch handler script: localStorage
_pwmark наtouchstart/mousedownдля tabunder pre-detect.
CSS site-side (адаптивні правки)
- Tooltip overlap:
.<actions>{position:relative;z-index:5001}— підняти actions row над.asg-container{z-index:5000}. Plus.<msg-class>{z-index:10000}як defense. - Timeline thumb hover-play:
.<thumbs-block> .<item>.<can-play> a:hover .<img-class>:after{display:block}+ mobile@media(max-width:768px){.<thumbs> ... :after{transform:translate(-50%,-50%) scale(.6)}}. - Title wrap (
.heading-2truncation):.<title-head> .<heading-2>{white-space:normal;overflow-wrap:anywhere;...}— override будь-який single-line ellipsis. .<thumbs-block>mobile scaling — adapt 8148 patterns до native classes site-у.
Verification
ad-test "<test_url>"→ 6/6 PASS.- Empirical Playwright probe: capture
pageerrorarray — має бути empty. Перевірити specificallyc is not defined,lazyLoadFunc is not defined,c2.min.js Unexpected identifier 'o'. - Validate
c2.min.jssyntax:node --check views/static/js/c2.min.js— passing. Якщо sourcec2.jsedited без re-minify → brokenletdeclarations у comma-sequence; fix:terser views/static/js/c2.js -c -m -o views/static/js/c2.min.js. - Banner DOM uniqueness:
await page.evaluate(() => Array.from(document.querySelectorAll('iframe[src*="api/spots"]')))collect → no duplicate parent IDs (#thmb12× = bug). - VAST endpoint probe:
curl 'https://a5.g--o.info/api/users/<vast_spot>?v2=1&fill=0&url=<page-url>'returns<Ad>(не empty<VAST/>— no-fill). - Popunder endpoint probe:
curl 'https://a5.g--o.info/api/users/<popunder_spot>?host=<domain>&...'returnswindow.__NA.renderSpot({...}). - t1 CDN serves fresh bundle:
curl -sL "https://t1.<domain>/static/js/ad-bundle.min.js" | grep -oE 'spot:"[0-9]+"'— bundle reflects новий config. - Real-browser smoke: click play → VAST shows (after popunder cooldown set), click like → tooltip opens above player chrome, hover related thumb → play icon, hover timeline scene → smaller play icon (mobile) / regular (desktop).
Post-deploy
- Monitor adspyglass dashboard після prod deploy — graph має продовжувати ріст; падіння = регресія.
- Update site row у SITES.md з generation tag (
v3 PlayerJS). - Update memory
project_player_roadmap.md— додати site до v3 list.
ASG SDK filename patterns (anti-footprint)
a5.g--o.info/<filename>.js server tolerates будь-який filename matching pattern per type — content однаковий. Use unique per site щоб diluteти footprint (adblock fingerprints).
Patterns (verified 2026-05-05, refined post-bug):
- POPUNDER: 7 chars total,
^[A-Za-z0-9]{6}[13579]$— last char must be ODD DIGIT only. Even digits return DIFFERENT SDK content (different ad type, will break renderSpot). - VAST: 7 chars total,
^[A-Za-z0-9]{6}[abcdABCD]$— last char must be a-d (8 chars). e-h returns different VAST sub-type (incompatible). - Verify content size post-generation: popunder=250844b, vast=323450b. Different size → wrong SDK fetched.
- BANNER
tb_config.banner_source: локальний файл уviews/static/js/<name>.jsper site, не ASG endpoint — окремий footprint layer (rename файла + update reference)
Workflow: generate per-site filenames → verify via curl -sI → apply mass-replace у ad-config.js (popunder.sdk, vast.sdk) → rebuild bundle → update ?v=<md5> cache-bust у layout.
Detail: memory reference_ad_filename_patterns (15-site mapping applied 2026-05-05).
Monitoring
adspyglass dashboard — best diagnostic signal
https://app.adspyglass.com/dashboard
Чому це best signal:
- Live графік показу реклами per site
- Падіння графіка = реклама не показується = ймовірно поломка (frontend/template/JS error / banned mirror / CDN cache stale)
- Ріст = трафік підріс або UX покращився
Юзер каже: "більший показник саме для мене це лайв графік реклами від АСГ" — реклама падає миттєво коли щось не так, ще до того як прийдуть алерти.
Інші alerts
- Telegram + email — критичні (downtime, errors)
- Billing dashboard
https://billing.g--o.info/cs/oursites/dashboard/— статистика (зазвичай не дивимось)
Template patterns
Pattern A (8148 modern):
<link rel="preconnect" href="https://a5.g--o.info" crossorigin>
<script src="/static/js/ad-bundle.min.js?v=<%= ad_bundle_v %>" defer></script>
...
<% render("views.modules.banners.header_mobile_adspy") %>
<% render("views.modules.banners.footer_mobile_adspy") %>
<% render("views.modules.banners.footer_descktop_adspy") %>
Pattern C (8112 legacy inline):
<link rel="dns-prefetch" href="//surstrom.com">
...
<script async src="//surstrom.com/qDap9.js"></script>
<iframe class="na" src="//surstrom.com/api/spots/72437?p=1" sandbox="..." loading="lazy"></iframe>
...
tb.load_frame_baner_v2("//surstrom.com/api/spots/72437?p=1","#tb0",{...},{...});
Що треба з'ясувати + задокументувати
Architecture migration
- Чи планується мігрувати pattern C → B/A (legacy → modern)?
- 8148 — pilot для bundle-architecture, чи поширюватиметься?
CSP / Security
- CSP exceptions для зеркал — як підтримуються?
- Mirror swap — чи треба update CSP при заміні?
Performance impact
- PSI вплив реклами — % LCP / TBT (per pattern)?
- Lazy-load ads below-fold — реалізовано (видно
loading="lazy"на iframes)?
VAST flow (8148 detailed)
User experience
- Користувач клацає play на
pjs_play_btn(PlayerJS player) asgInVideoImpressionevent від ASG SDK →_decideAndApplyловить → запускаєVastPreroll.show()- ASG іnject-ить
.asg-containerз video preroll MutationObserverловить container → pin pointer + z-index 5000, добавляє progress bar (.vast-progressyellow #ffd000)- Coordinated mute: video у container синхронізується з
_vastMutedlocalStorage (per-tab persist) - Після VAST done (video.ended OR
currentTime >= duration - 0.2) → callbackwindow._asgAdDone()→ main video starts viawindow._revealPjs() enableMediaPlayHijack: trueблокує race у HTMLMediaElement.play() якщо_adsLocked— попереджає 1-frame audio artifact коли VAST стартує над main video
Cooldown management
- ASG локальне сховище:
localStorage.asgsl— pipe-separated<spot_id>=<key>:<value>записи. Ключі що використовуються нашим bundle:global_rr:<timestamp>—_readCooldownsparse-ить це як cooldown end timen:<timestamp>— impression handler копіює це у_popRr/_vastRrcookies+LS
- Дублюючі cookies:
_popRr,_vastRr. Реплікуються з asgsl при impression event. - Декрипт пам'яті: дві ключові події з ASG —
asgPopunderImpression,asgInVideoImpression— тригерять запис cooldown.
⚠️ ASG spot config gotcha — frequency-capping mandatory
Bug 2026-05-02 (8148, after account recreation): popunder spot 514208 у новому ASG account-і був налаштований з shows_limit:1 без frequency-capping rotation. Симптоми:
- popunder СПрацьовує (видно
asgPopunderImpression) - Але
asgslлокально вигляд:<spot>=keep_looping:false,tabunder:false,uuid:...,noloop:true,shows_limit:1 - Нема
n:<timestamp>→ impression handler виходить без write_popRr - Нема
global_rr:<timestamp>→_readCooldownsповертаєpopActive=false - → mode завжди "pop" → popunder fires кожен pageload → VAST SDK ніколи не loadиться
Fix у ASG admin (на стороні юзера, не у нас):
- Відкрити spot config → знайти "Global rotation" / "Frequency capping" / "Cooldown" / "Time-based capping" / "Show frequency"
- Встановити, наприклад, "once per 2 hours per user" — це додасть
n:/global_rr:поля в asgsl - Те саме для VAST spot — інакше після популярного pop firing, VAST mode triggered але cooldown поломаний
Як перевірити: після popunder fire відкрий DevTools → localStorage.getItem('asgsl') → шукай global_rr: або n:. Якщо нема → не налаштовано.
⚠️ ASG no-fill diagnostic — VAST returns empty <VAST/>
Bug 2026-05-02 (8148, VAST never visible): Mode перемикався у "vast", VAST SDK loaded, але реклама не з'являлась візуально.
Перевірка endpoint напряму:
curl 'https://a5.g--o.info/api/users/<vast_spot>?v2=1&fill=0&url=<page-url>'
Якщо response:
<?xml version="1.0" encoding="UTF-8"?>
<VAST version="3.0">
</VAST>
— це VAST no-fill. ASG не має inventory для цього spot.
Порівняй з popunder endpoint який повертає повний JSON-like config з creative_id, behavior, тощо:
window.__NA.renderSpot({ spot_id: ..., creative_id: 83660, config: {...} });
Можливі причини no-fill (по пріоритету):
- Spot type ≠ "In-video VAST" — створено як Banner/Popunder/etc → VAST endpoint не має чого serve
- Spot Status pending review — створено, чекає approval
- Creative не attached — VAST потребує специфічний ад чи bidder pool
- Bidder / network не linked — VAST джерело мережі (ExoClick VAST / TS / etc) не призначено
- Budget / cap exhausted
Як остаточно verify: через playwright headless + manual inject _popRr форсити VAST mode → click play → дивитися чи з'являється .asg-container. Якщо ні + curl показує empty VAST → no-fill.
Mode decision (AdCore._decide)
mode = popActive ? (vastActive || skipPattern ? "none" : "vast") : "pop"
| popunder | vast | mode |
|---|---|---|
| available (cooldown OFF) | — | pop (priority) |
| у cooldown (recent fire) | available + не skip-pattern | vast |
| у cooldown | у cooldown OR skip-pattern | none |
Implicit dependency: VAST показується тільки після того як popunder уже спрацював і поставив cooldown. На свіжій сесії (нема _popRr у localStorage / cookies) → mode завжди "pop", VAST SDK навіть не завантажується.
⚠️ Це gotcha при тестуванні: якщо щойно фікснули popunder spot — VAST не з'явиться поки popunder не спрацює хоча б раз. Юзер має думати що "VAST зламаний", а він просто заблокований mode logic.
Force VAST testing: ?clearAds=1 URL param очищає _popRr/_vastRr/_vastPatternIdx/_pw/asgsl/_pjsLog — фрешний state. Тоді: page load → pop fires → cooldown set → refresh → mode=vast → VAST SDK loaded.
vastPolicy "show-1-skip-1" додатково: idx=0 → SHOW, idx=1 → SKIP (incremented per VAST impression). На свіжій сесії idx=0 → перша VAST показується, друга skip-неться.
Policy choice
pop-priority policy + show-1-skip-1 для VAST — навмисний баланс ревеню vs UX:
- Popunder показується відносно рідко (cap від ASG високий)
- VAST спрацьовує часто, тому штучно скачуємо до 50% (
show-1-skip-1) — менше тиску на юзера, не вбиває engagement - Якщо колись треба максимізувати ревеню →
vastPolicy: 'show-always'
Open questions for developer
- Як здійснюється mirror swap — sed-replace у всіх template/JS файлах через Адміна, runtime config, чи інший механізм?
- Які pattern використовується для НЕ-77 сайтів (17 без adspy banner modules) — special-case structure, "no ads" сайти, чи щось інше?
- SDK URLs (
nDNVal3.js,9iO21Eb.js) — чи стабільні чи можуть змінитися при mirror rotation?