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="footer" data-sb-site="yoursite.com"></div>
<script src="https://sponsorslot.dogfooded.com/dfu/s.js" async></script>

data-sb-slot is the slot's name, as you see it in your console. data-sb-site is the site you registered: you can leave it out on that exact domain, and need it on a subdomain. The script sets no cookies and measures nothing but its own visibility.

Works the same on a single-page app, Next.js included: a container that shows up after a client-side navigation is filled as soon as it lands in the page.

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.

Your project hereOne sentence, one link.Sponsored

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.

Your project hereOne sentence, one link.Sponsored
<div data-sb-slot="footer"></div>

Write it like a prop, on the container itself. Nothing is stored on our side, so three slots on the same page can use three layouts.

<div data-sb-slot="footer" 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 never quietly drops your numbers.

Arrow

An arrow in the bottom right corner tells visitors the whole card is a link. It is off by default: add the attribute to the container to turn it on.

Your project hereOne sentence, one link.Sponsored
<div data-sb-slot="footer"></div>
<div data-sb-slot="footer" data-sb-arrow></div>

It takes its color from the slot. Size it with --sb-arrow-size and soften it with --sb-arrow-opacity.

Hover

Give the card a response when a visitor points at it, or reaches it with the keyboard. Hover the preview to try each one.

Your project hereOne sentence, one link.Sponsored
<div data-sb-slot="footer" data-sb-arrow></div>

Put it on the slot, or once on a wrapper to cover every slot inside.

<div class="sponsors" data-sb-hover="lift">
  <div data-sb-slot="place-1"></div>
  <div data-sb-slot="place-2"></div>
</div>

Each effect uses your text color, and --sb-hover-color sets another one. With the arrow on, it moves toward the corner too. Visitors who asked their system for less motion get the effect without the movement.

Grid

One container per slot, in your own grid.

PolishAutonomous A/B testing for your landing page.Sponsored
CoaxalManual prompt-injection testing for AI agents.Sponsored
DogfoodedA studio shipping its own small products.Sponsored
<div class="sponsors">
  <div data-sb-slot="place-1" data-sb-variant="stack"></div>
  <div data-sb-slot="place-2" data-sb-variant="stack"></div>
  <div data-sb-slot="place-3" data-sb-variant="stack"></div>
</div>

<style>
  .sponsors { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px }
</style>

List

One container per slot, stacked.

PolishAutonomous A/B testing for your landing page.Sponsored
CoaxalManual prompt-injection testing for AI agents.Sponsored
DogfoodedA studio shipping its own small products.Sponsored
<div data-sb-slot="place-1" data-sb-variant="list"></div>
<div data-sb-slot="place-2" data-sb-variant="list"></div>
<div data-sb-slot="place-3" data-sb-variant="list"></div>

Rotation

Put several slot names on one container and give it an interval in seconds. The slot moves from one sponsor to the next.

PolishAutonomous A/B testing for your landing page.Sponsored
<div data-sb-slot="place-1,place-2,place-3" data-sb-rotate="1"></div>

Each name 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.

PolishAutonomous A/B testing for your landing page.Sponsored
<div data-sb-slot="place-1,place-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.

Skins

Three ready-made sets of tokens. Copy the one you want into your own stylesheet and edit it from there: they are plain CSS variables, not a setting you have to come back here to change.

Your project hereOne sentence, one link.Sponsored

Light and dark

Your page already knows whether it is light or dark. Wrap the tokens in the media query you use everywhere else, and the slot follows the same rule as the rest of your site, including when a visitor flips the preference mid-session.

Your project hereOne sentence, one link.Sponsored
[data-sb-slot] {
  --sb-bg: #ffffff;
  --sb-border: #e6e3de;
}

The page behind it does not change here, only the tokens do. A dark card works on a light page and the other way round.

Tokens

Set them from your own stylesheet. The slot follows without you overriding a single class, and there is nothing to save anywhere.

[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.

20 px
PolishAutonomous A/B testing for your landing page.Sponsored
28 px
PolishAutonomous A/B testing for your landing page.Sponsored
44 px
PolishAutonomous A/B testing for your landing page.Sponsored
[data-sb-slot] { --sb-logo: 44px; --sb-logo-radius: 10px; }
TokenDefaultEffect
--sb-bgtransparentslot background
--sb-colorinherittext color
--sb-bg-imagenonegradient or image behind it
--sb-border18% of your text colorborder color
--sb-border-w1pxborder width
--sb-radius8pxcorner radius
--sb-pad10px 12pxinner density
--sb-gap10pxspace between logo and text
--sb-logo28pxlogo size
--sb-logo-radius4pxlogo corner radius
--sb-title-colorinheritheadline color
--sb-title-weight600headline weight
--sb-body-opacity.75sentence opacity
--sb-body-size.92emsentence size
--sb-label-size10pxsponsored label size
--sb-label-opacity.6sponsored label opacity
--sb-arrow-size16pxarrow size
--sb-arrow-opacity.6arrow opacity
--sb-hover-coloryour text colorhover effect color
--sb-carousel-width260pxwidth of a carousel card
--sb-carousel-gap16pxspace between carousel cards

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; }
ClassElement
.sbuthe link wrapping everything
.sbu--row, .sbu--stack…the layout, on the same element
.sbu-logothe sponsor's image
.sbu-textthe headline + sentence block
.sbu-titlethe headline
.sbu-bodythe sentence
.sbu-labelthe 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="footer">
  <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>
AttributeWhat 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.

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.

Your project hereOne sentence, one link.Sponsored
Not counted (needs 120 × 24)no label (needs ≥ 9px, readable)

A slot shrunk to two pixels or hidden does not get you a warning and does not get you removed. It simply is not counted. We never argue with your CSS, we decline to count.

Thresholds

ConditionThreshold
Visible surface50% of pixels
Continuous duration1 second
Rendered sizeat 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://sponsorslot.dogfooded.com/dfu/:path*",
    }];
  },
  skipTrailingSlashRedirect: true,
};
// vercel.json
{
  "rewrites": [
    {
      "source": "/dfu/:path*",
      "destination": "https://sponsorslot.dogfooded.com/dfu/:path*"
    }
  ]
}

Then load the script from your own domain. The rewrite does nothing while the tag still points at ours:

<script src="/dfu/s.js" async></script>

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.

The trade-off: requests then reach us through your server, so visitors can no longer be told apart. Impressions and clicks stay exact, but reports show unique visitors as a lower bound, or n/a when all traffic went through the rewrite.

Everything works without it, with a less reliable blocked-load measurement and exact unique visitors.