A lightweight, privacy-focused ad engine for displaying targeted advertisements to iOS and macOS users. Supports popups, sliders, faders, and in-content banners with fully customizable configurations.
- Features
- Partner Integration Guide
- Ad Types
- Configuration Reference
- Development
- CI/CD Pipeline
- Adding a New Partner
- Platform Targeting: Automatically detects and displays ads only on iOS and macOS devices
- Multiple Ad Formats: Popups, in-content banners, sliders, and faders
- Rotating Content: Support for multiple slides with configurable rotation intervals
- Rotating Titles: Independent title rotation for popup ads
- Image Preloading: Automatically preloads upcoming slides for smooth transitions
- Responsive Design: Separate image URLs for desktop and mobile viewports
- Zero Dependencies: Vanilla JavaScript with no external libraries required
- Lightweight: Minified bundle under 5KB
- Easy Integration: Simple script tag inclusion with remote configuration
Your personalized config file is located in the dist/configs/ directory. Use the static host URL:
https://static-ads.smarter.day/configs/YOUR_NAME.config.js
Example:
https://static-ads.smarter.day/configs/foodshelf.life.config.js
In all integration snippets below, the cache-busting value is generated in JavaScript from the current date (YYYYMMDD) and appended as ?v=<date>.
Choose the integration method that best fits your technology stack.
Add these script tags before the closing </body> tag:
<!-- Smarter.day Ads Engine -->
<script>
const adsVersion = new Date().toISOString().slice(0, 10).replace(/-/g, "");
window.AD_CONFIG_URL = `https://static-ads.smarter.day/configs/YOUR_NAME.config.js?v=${adsVersion}`;
const adsScript = document.createElement("script");
adsScript.src = `https://static-ads.smarter.day/index.js?v=${adsVersion}`;
adsScript.async = true;
document.body.appendChild(adsScript);
</script>Using Next.js Script Component (Recommended for Next.js):
// components/AdsEngine.jsx
import Script from "next/script";
export default function AdsEngine() {
return (
<>
<Script
id="ads-engine-loader"
strategy="afterInteractive"
>{`
(() => {
const adsVersion = new Date().toISOString().slice(0, 10).replace(/-/g, "");
window.AD_CONFIG_URL = "https://static-ads.smarter.day/configs/YOUR_NAME.config.js?v=" + adsVersion;
const script = document.createElement("script");
script.src = "https://static-ads.smarter.day/index.js?v=" + adsVersion;
script.async = true;
document.body.appendChild(script);
})();
`}</Script>
</>
);
}Using React Helmet or standard React:
// components/AdsEngine.jsx
import { useEffect } from "react";
export default function AdsEngine() {
useEffect(() => {
const adsVersion = new Date().toISOString().slice(0, 10).replace(/-/g, "");
window.AD_CONFIG_URL =
`https://static-ads.smarter.day/configs/YOUR_NAME.config.js?v=${adsVersion}`;
const script = document.createElement("script");
script.src = `https://static-ads.smarter.day/index.js?v=${adsVersion}`;
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
return null;
}Include the component in your layout or root component.
Nuxt 3 Plugin (plugins/ads-engine.client.ts):
export default defineNuxtPlugin(() => {
if (typeof window !== "undefined") {
const adsVersion = new Date().toISOString().slice(0, 10).replace(/-/g, "");
window.AD_CONFIG_URL =
`https://static-ads.smarter.day/configs/YOUR_NAME.config.js?v=${adsVersion}`;
const script = document.createElement("script");
script.src = `https://static-ads.smarter.day/index.js?v=${adsVersion}`;
script.async = true;
document.body.appendChild(script);
}
});Vue 3 Composable:
// composables/useAdsEngine.ts
import { onMounted, onUnmounted } from "vue";
export function useAdsEngine(configUrl: string) {
let script: HTMLScriptElement | null = null;
onMounted(() => {
const adsVersion = new Date().toISOString().slice(0, 10).replace(/-/g, "");
const separator = configUrl.includes("?") ? "&" : "?";
window.AD_CONFIG_URL = `${configUrl}${separator}v=${adsVersion}`;
script = document.createElement("script");
script.src = `https://static-ads.smarter.day/index.js?v=${adsVersion}`;
script.async = true;
document.body.appendChild(script);
});
onUnmounted(() => {
if (script) {
document.body.removeChild(script);
}
});
}If your configuration includes in-content banner ads, add placeholder elements with matching CSS selectors:
<!-- Ad slot that matches "#ad-slot" selector in config -->
<div id="ad-slot"></div>
<!-- Ad slot that matches ".sidebar-ad" selector in config -->
<div class="sidebar-ad"></div>The ads engine automatically populates these containers when the page loads.
A modal overlay that appears after a configurable delay.
| Feature | Description |
|---|---|
| Delay | Configurable appearance delay (default: 5 seconds) |
| Close Button | Appears after 5 seconds to prevent accidental dismissal |
| Titles | Rotating headlines displayed above the image |
| Slides | Multiple images with independent rotation |
| Effects | Supports fade and slide transitions |
Embedded ad units that render inside designated containers on your page.
| Feature | Description |
|---|---|
| Targeting | Uses CSS selectors to find placement containers |
| Responsive | Automatically fits container dimensions |
| Rotation | Multiple slides with configurable intervals |
| Effects | Supports fade and slide transitions |
Each config file defines window.AD_CONFIG with the following structure:
(() => {
const CURRENT_YEAR = new Date().getFullYear();
window.AD_CONFIG = {
// In-content banner (CSS selector as key)
"#ad-slot": {
interval: 4000, // Rotation interval in milliseconds (0 = disabled)
effect: "fade", // Transition effect: "fade" or "slide"
slides: [
{
desktop: "https://example.com/banner-desktop.webp",
mobile: "https://example.com/banner-mobile.webp",
link: "https://example.com/landing-page",
},
// Additional slides...
],
},
// Popup configuration
popup: {
delay: 5000, // Delay before showing popup (milliseconds)
interval: 5000, // Slide rotation interval (milliseconds)
effect: "fade", // Transition effect: "fade" or "slide"
titleInterval: 4000, // Title rotation interval (milliseconds)
titles: [
"Your Headline Here",
`Special Offer for ${CURRENT_YEAR}`,
],
slides: [
{
desktop: "https://example.com/popup-desktop.webp",
mobile: "https://example.com/popup-mobile.webp",
link: "https://example.com/app-store-link",
},
// Additional slides...
],
},
};
})();| Property | Type | Required | Description |
|---|---|---|---|
interval |
number |
No | Rotation interval in ms. Set to 0 to disable rotation. |
effect |
string |
No | Transition effect: "fade" (default) or "slide". |
slides |
array |
Yes | Array of slide objects. |
| Property | Type | Required | Description |
|---|---|---|---|
delay |
number |
No | Delay before showing popup (default: 5000ms). |
interval |
number |
No | Slide rotation interval in ms. |
effect |
string |
No | Transition effect: "fade" (default) or "slide". |
titleInterval |
number |
No | Title rotation interval in ms. |
titles |
array |
No | Array of headline strings. |
slides |
array |
Yes | Array of slide objects. |
| Property | Type | Required | Description |
|---|---|---|---|
desktop |
string |
Yes | Image URL for viewports ≥768px wide. |
mobile |
string |
Yes | Image URL for viewports <768px wide. |
link |
string |
Yes | Destination URL when clicked. |
- Disable popup: Remove or comment out the
popupkey from the config. - Disable a banner slot: Remove or comment out the corresponding selector key.
- Disable rotation: Set
intervalto0.
- Node.js 18 or higher
- npm or yarn
git clone https://github.com/smarter-day/ads.git
cd ads
npm installnpm run buildThis command:
- Bundles and minifies
index.js→dist/index.js - Minifies all config files in
configs/→dist/configs/ - Optimizes images from
images/→dist/images/as WebP (max 1920x1920) - Rewrites built config image paths (
images/...) tohttps://static-ads.smarter.day/images/... - Minifies
index.html→dist/index.html
- Put source screenshots in
images/and reference them in partner configs asimages/<file>.<ext>. - Supported source formats:
.png,.jpg,.jpeg,.webp,.avif. - During build, all supported images are converted to optimized
.webpand copied todist/images/. - Source
configs/*.jsare unchanged for local root testing (index.html+./configs/...+./images/...). - Built
dist/configs/*.config.jsget production image URLs:https://static-ads.smarter.day/images/<file>.webp.
Start a local server to test the ads engine:
npx serve .Then open http://localhost:3000 in a browser. For accurate testing, use Safari on macOS or the iOS Simulator.
ads/
├── .github/
│ └── workflows/
│ └── build.yml # CI/CD pipeline
├── configs/
│ └── foodshelf.life.config.js # Partner configurations (source)
├── images/
│ └── ... # Source screenshots (original formats)
├── dist/
│ ├── configs/
│ │ └── foodshelf.life.config.js # Minified configs (auto-generated)
│ ├── images/
│ │ └── ... # Optimized WebP assets (auto-generated)
│ ├── index.html # Minified test page
│ └── index.js # Minified ads engine
├── build.mjs # Build script
├── index.html # Test page (source)
├── index.js # Ads engine (source)
├── package.json
└── README.md
The repository includes a GitHub Actions workflow that automatically:
- Triggers on every push to
mainormasterbranches - Installs dependencies using
yarn install --frozen-lockfile - Builds and minifies all source files
- Uploads the
dist/folder to a Cloudflare R2 bucket (S3-compatible)
This ensures partners always have access to the latest minified files from your R2 deployment target.
CLOUDFLARE_R2_ACCESS_KEY_IDCLOUDFLARE_R2_SECRET_ACCESS_KEY
CLOUDFLARE_ACCOUNT_IDCLOUDFLARE_R2_BUCKETCLOUDFLARE_R2_PREFIX(optional folder prefix in the bucket)
Located at .github/workflows/build.yml.
-
Create the config file:
cp configs/foodshelf.life.config.js configs/partner-name.config.js
-
Customize the configuration:
- Update slide images and links
- Modify popup titles
- Adjust timing intervals as needed
-
Push to the repository:
git add configs/partner-name.config.js git commit -m "Add partner-name config" git push origin main -
Share the integration details:
Config URL:
https://static-ads.smarter.day/configs/partner-name.config.jsEngine URL:
https://static-ads.smarter.day/index.js
The ads engine targets Apple platforms and supports:
- Safari on macOS (all versions)
- Safari on iOS/iPadOS (all versions)
- Chrome, Firefox, and Edge on macOS
The engine automatically exits on non-Apple platforms without executing any code.
Proprietary. All rights reserved.
For questions or issues, contact the Smarter.day team.