Skip to content

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 config

Key Resources

ResourceLocation
Repogit@github.com:Cavai/delivery-3.git (local: /Users/nicolay/CavaiProduct/delivery-3)
Whitelist JSONdelivery-3/files/skin-domain-whitelist-v1.json
VPAID sourcedelivery-3/src/vpaid.ts
Skin Domains spreadsheetGoogle Sheets
Output JSON docGoogle Docs
Local Excel backupCavai-Documentation/src/DocumentationTexts/skin-delivery/Skin domains.xlsx
CDN base URLhttps://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

  1. Open the Skin Domains spreadsheet
  2. Find the domain row (or add a new one)
  3. Update the selector, z-index, padding, and/or additional JS columns
  4. Click "Update Output" button in the spreadsheet to regenerate the JSON
  5. Copy the JSON from the Output Google Doc
  6. Paste into delivery-3/files/skin-domain-whitelist-v1.json
  7. Commit and push to main -- the file auto-deploys to CDN
  8. 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: visible and 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-pos for the skin's internal positioning
  • Scroll handling: Adding scroll listeners to update --scroll-pos so 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).

Internal documentation