Docs
One slot, on your page, in one paste.
You already sell sponsorships by hand. This replaces the part where you edit your HTML every time the sponsor changes, and keeps the look you already have.
Drop in the tag
One container where the slot goes, and the script once in the page. The container is yours: move it, size it, style it.
<div data-sb-slot="your_key"></div>
<script src="https://sponsorsdk.dogfooded.com/dfu/s.js" async></script>You get the key when you create the slot. The script is 2 KB, sets no cookies, and measures nothing but its own visibility.
It already looks like your page
Every value in the slot is a CSS variable whose default inherits from your page: the typeface, the text color, and a border derived from that same color. You get this without configuring anything.
Layouts
A layout decides where the logo, the text and the label go. It does not decide colors or borders: that is theming, and the two compose.
<div data-sb-slot="your_key"></div>Write it like a prop. It wins over whatever is set in your dashboard, so three slots on the same page can use three layouts.
<div data-sb-slot="your_key" data-sb-variant="stack"></div>Whichever one you pick, we render the sponsored label ourselves. An impression is only counted when that label is readable, so a layout you did not build could never quietly cost you your earnings.
Skins
Three sets of tokens you can pick in one click from your dashboard. Card carries its own dark values, switched on the visitor's system preference.
Light and dark
A skin can carry two sets of values. The Card skin does: we serve both with the slot, and the script switches on the visitor's system preference, then switches again if it changes mid-session.
The page behind it does not change here. It is the slot that carries its own light and dark values, so a dark card works on a light page and the other way round.
Tokens
Set them from your own stylesheet, no dashboard needed. The slot follows without you overriding a single class.
[data-sb-slot] { --sb-radius: 14px; --sb-pad: 14px 16px; }The logo is the one most people reach for first. Here is the same slot at three sizes, so you can judge them against each other rather than one at a time.
[data-sb-slot] { --sb-logo: 44px; --sb-logo-radius: 10px; }| Token | Default | Effect |
|---|---|---|
| --sb-bg | transparent | slot background |
| --sb-color | inherit | text color |
| --sb-bg-image | none | gradient or image behind it |
| --sb-border | 18% of your text color | border color |
| --sb-border-w | 1px | border width |
| --sb-radius | 8px | corner radius |
| --sb-pad | 10px 12px | inner density |
| --sb-gap | 10px | space between logo and text |
| --sb-logo | 28px | logo size |
| --sb-logo-radius | 4px | logo corner radius |
| --sb-title-color | inherit | headline color |
| --sb-title-weight | 600 | headline weight |
| --sb-body-opacity | .75 | sentence opacity |
| --sb-body-size | .92em | sentence size |
| --sb-label-size | 10px | sponsored label size |
| --sb-label-opacity | .6 | sponsored label opacity |
A layout may change a default, never your value: the layout rules redeclare the same token with a different fallback, so anything you set explicitly still wins.
--sb-bg-image takes a gradient or an image, and stacks over --sb-bg. A gradient is a background image in CSS, not a color, which is why they are two tokens rather than one.
[data-sb-slot] {
--sb-bg: #12101c;
--sb-color: #ece9f5;
--sb-bg-image: linear-gradient(120deg, #6d3ff211, transparent 60%);
}
/* Une image de fond, en https et entre guillemets. */
[data-sb-slot] { --sb-bg-image: url("https://cdn.example.com/texture.png") }This is the one token allowed to use parentheses, so it is validated on its own terms: balanced parentheses, an allowlist of CSS functions, and any url() restricted to https in quotes. Everything else stays on the strict rule.
Your own CSS
The class names are stable, and our stylesheet sits at deliberately low specificity. You override it from yours without a single !important.
[data-sb-slot] .sbu {
border: none;
background: #fafafa;
font-family: "My Typeface", serif;
}
[data-sb-slot] .sbu-title { color: #b4530a; }
[data-sb-slot] .sbu-label { opacity: .45; }| Class | Element |
|---|---|
| .sbu | the link wrapping everything |
| .sbu--row, .sbu--stack… | the layout, on the same element |
| .sbu-logo | the sponsor's image |
| .sbu-text | the headline + sentence block |
| .sbu-title | the headline |
| .sbu-body | the sentence |
| .sbu-label | the sponsored label |
Your own markup
You already have a sponsor card that matches your site. Keep it as it is, mark the parts to fill, and we stop building anything.
<div data-sb-slot="your_key">
<a data-sb="cta" class="my-card">
<img data-sb="logo" alt="" />
<span>
<strong data-sb="title"></strong>
<em data-sb="body"></em>
</span>
<small data-sb="sponsored"></small>
</a>
</div>| Attribute | What we put in it |
|---|---|
| data-sb="logo" | the src if it is an <img>, otherwise a background |
| data-sb="title" | the sponsor's headline |
| data-sb="body" | their sentence |
| data-sb="cta" | the link, if the element is an <a> |
| data-sb="sponsored" | the label, if you leave the element empty |
Every anchor is optional, the label included: it only decides where the label lands in your card. Leave it out and we place it ourselves, or keep the mention you already wrote.
If you do not already have a card you care about, take a layout instead.
A grid of sponsors
One container per slot, in your own grid. Each is a separate slot with its own key and its own sponsor, so booking one never moves what runs in another.
<div class="sponsors">
<div data-sb-slot="key_1" data-sb-variant="stack"></div>
<div data-sb-slot="key_2" data-sb-variant="stack"></div>
<div data-sb-slot="key_3" data-sb-variant="stack"></div>
</div>
<style>
.sponsors { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px }
@media (max-width: 640px) { .sponsors { grid-template-columns: 1fr } }
</style>Rotation
Put several keys on one container and give it an interval in seconds. The slot moves from one sponsor to the next.
<div data-sb-slot="key_1,key_2,key_3" data-sb-rotate="1"></div>Each key stays a distinct slot. Rotation shares a position, it does not turn one slot into ten.
Each one is measured on its own, so a slot shown one turn in three reports the impressions it actually got, not a third of the page.
The interval has a floor of 4 seconds.
An impression needs 1 continuous second on screen. Rotating faster than that would mean no slide ever reaches the threshold: a perfect looking carousel, counting nothing. Ask for less and we hold the floor.
Animations
Rotation uses a cross fade by default. Add data-sb-anim to change it, on a rotating slot or on a static one you want to appear rather than pop in.
<div data-sb-slot="key_1,key_2" data-sb-anim="dissolve" data-sb-rotate="8"></div>Nothing moves unless you ask: without the attribute the slot appears the way it always has. And a visitor who asked their system for less motion gets none of it, the content still changes.
A ranked list
For a directory, a ladder reads better than a grid. Position one is worth more than position five, and you price them accordingly.
<ol class="featured">
<li><div data-sb-slot="rank_1" data-sb-variant="list"></div></li>
<li><div data-sb-slot="rank_2" data-sb-variant="list"></div></li>
<li><div data-sb-slot="rank_3" data-sb-variant="list"></div></li>
</ol>Create the whole ladder in one go from your dashboard: pick a count and we number the slots for you.
What counts as an impression
You style the slot however you like, down to supplying your own markup. In exchange, an impression is only counted when it was genuinely seen. That is the whole deal, and the only thing we hold.
Squeeze the slot and watch the verdict change. The dimensions below are measured on the element you are looking at, against the same thresholds the script applies on your own page.
A slot shrunk to two pixels or hidden does not get you a warning and does not get you removed. It simply earns nothing. We never argue with your CSS, we decline to count.
Thresholds
| Condition | Threshold |
|---|---|
| Visible surface | 50% of pixels |
| Continuous duration | 1 second |
| Rendered size | at least 120 × 24 px |
| Readable label | ≥ 9 px, contrast ≥ 3:1 |
Contrast is measured against the real background behind the label, with its opacity folded in, not against what the CSS claims.
A request stopped by an ad blocker costs you volume and never costs anyone integrity. The script reports that it failed to start, so a blocked page reads as blocked rather than as an audience that did not look.
Serve it from your own domain
Optional, and recommended. One rewrite rule and the slot becomes same-origin.
// next.config.js
export default {
async rewrites() {
return [{
source: "/dfu/:path*",
destination: "https://sponsorsdk.dogfooded.com/dfu/:path*",
}];
},
skipTrailingSlashRedirect: true,
};// vercel.json
{
"rewrites": [
{
"source": "/dfu/:path*",
"destination": "https://sponsorsdk.dogfooded.com/dfu/:path*"
}
]
}Latency is the small reason. The real one is that it makes your blocked-load figure trustworthy. Served from a third-party domain, the signal that says the script never started falls under the same filter rule as the script it was meant to report on, so a blocked page looks exactly like a page nobody looked at.
Everything works without it, with a less reliable blocked-load measurement.