Appearance
Skin (HPTO/Takeover) Delivery System
Owner: Haakon Mydland (@mcsneaky) -- on leave July 2026, Nicolay covering.
Overview
"Skins" are full-page takeover ads (HPTO = High-impact Page Take Over) that wrap around a publisher's website content. The skin creative renders behind the page content with clickable left/right side panels and an optional top leaderboard.
The challenge: every publisher site has a different DOM structure, so the skin delivery script needs per-domain configuration to know which CSS selector identifies the main content container, what z-index to use, and sometimes custom JS to fix layout issues.
Architecture
Google Sheets (Skin Domains)
|
| "Update Output" button (Apps Script macro)
v
Google Docs (Output JSON)
|
| Manual copy-paste into repo
v
delivery-3 repo: files/skin-domain-whitelist-v1.json
|
| Auto-deployed on push to main
v
https://delivery-3.cavai.com/assets/general/skin-domain-whitelist-v1.json
|
| Fetched at runtime by skin delivery script (vpaid.ts)
v
Publisher page: skin renders with domain-specific configKey Resources
| Resource | Location |
|---|---|
| Repo | git@github.com:Cavai/delivery-3.git (local: /Users/nicolay/CavaiProduct/delivery-3) |
| Whitelist JSON | delivery-3/files/skin-domain-whitelist-v1.json |
| VPAID source | delivery-3/src/vpaid.ts |
| Skin Domains spreadsheet | Google Sheets |
| Output JSON doc | Google Docs |
| Local Excel backup | Cavai-Documentation/src/DocumentationTexts/skin-delivery/Skin domains.xlsx |
| CDN base URL | https://delivery-3.cavai.com/assets/general/ |
Whitelist JSON Structure
Each domain is a key (uppercase, e.g. "TELEGRAAF.NL") with:
json
{
"selector": "CSS selector for the main content container",
"zIndex": "z-index value (number string or 'unset')",
"padding": "padding in px (number string)",
"additionaljs": "base64-encoded JavaScript for domain-specific fixes"
}- selector: Identifies the content container that needs to sit above the skin. The skin renders behind this element.
- zIndex: Controls stacking order of the content relative to the skin.
- padding: Extra padding around the content area.
- additionaljs: Base64-encoded JS that runs after the skin loads. Used for site-specific DOM manipulation (repositioning iframes, fixing z-index issues, handling sticky headers, etc.).
Workflow for Fixing a Domain
- Open the Skin Domains spreadsheet
- Find the domain row (or add a new one)
- Update the selector, z-index, padding, and/or additional JS columns
- Click "Update Output" button in the spreadsheet to regenerate the JSON
- Copy the JSON from the Output Google Doc
- Paste into
delivery-3/files/skin-domain-whitelist-v1.json - Commit and push to main -- the file auto-deploys to CDN
- Note: Cloudflare CDN cache is NOT auto-purged. Changes may take up to 24h unless manually purged in Cloudflare dashboard.
Alternative: Direct JSON Edit
For urgent fixes, you can also directly edit skin-domain-whitelist-v1.json in the repo, commit, and push. Just make sure the spreadsheet is updated afterwards to stay in sync.
Common Fix Patterns (from Haakon's commits)
The additionaljs field (base64-encoded) typically contains a handleLoad() function. Common patterns:
- Repositioning the iframe: Moving
adPlacementIframeId(the skin iframe) by adjusting.style.top,.style.left,.style.width - Fixing z-index stacking: Setting
overflow: visibleand high z-index on wrapper elements so the skin is visible - Handling sticky headers: Grabbing header/nav elements, computing their height, and positioning the skin below them
- Removing blocking divs:
document.querySelector(".cavai-blocking-div").remove() - Setting CSS custom properties:
--content-width,--visibleHeight,--topOffset,--scroll-posfor the skin's internal positioning - Scroll handling: Adding scroll listeners to update
--scroll-posso the skin parallax/sticky behavior works correctly
Decoding additionaljs
bash
# Decode a base64 additionaljs field
echo "BASE64_STRING" | base64 -d
# Or with Python
python3 -c "import base64; print(base64.b64decode('BASE64_STRING').decode())"Encoding new JS
bash
# Encode JS to base64 for the spreadsheet/JSON
echo -n "function handleLoad() { ... }" | base64
# Or with Python
python3 -c "import base64; print(base64.b64encode(open('fix.js').read().encode()).decode())"Deployment
Files in delivery-3/files/ auto-sync to https://delivery-3.cavai.com/assets/general/ on push to main. The repo README notes that most assets should be considered immutable, but skin-domain-whitelist-v1.json is frequently updated (Haakon pushes updates regularly).