Silvr Shop This Image: Publisher Overlay API

Version 2026-10-11. Prepared by Silvr for publishing partners.

Summary

The Silvr overlay is one publisher-installed script tag that loads a small Silvr loader. Every heavy or secret part (image fetch, recognition, caching, quotas, result rendering) runs behind a Silvr-hosted service. The publisher adds the tag (directly or through their tag manager), allow-lists two Silvr hosts in their Content Security Policy, and is done. Finding images, placing the + over a specific watch, and showing the result all happen in Silvr-controlled code.

Why this shape:

One pass through the system:

  1. The loader finds candidate images as they scroll into view and batches their URLs to the proxy (POST /v1/detect).
  2. The proxy fetches each image server-side (no CORS problem), finds the items with Silvr's own detector, recognizes each one, stores the pins as 0 to 1 coordinates per image, and returns them. Cold images come back later; pre-processing keeps them rare.
  3. If at least one fashion item came back, the loader shows the Shop This Image affordance over the image.
  4. On activation the loader draws a + at each box, mapped onto the image as it is actually displayed (srcset variant, object-fit, carousel transform).
  5. Hover or click on a + asks the proxy for that item (GET /v1/item/{id}); the proxy returns the SKU match and similar items as a ready-to-insert HTML fragment, which the loader shows in a drawer beside the image.

The in-browser part is a small JavaScript loader that Silvr serves and configures.

1. How the overlay is delivered

Silvr delivers the overlay as an injected script because it is the only option that is reliable on pages Silvr does not control, fast once cached, and installable in under an hour. Injection through your own CDN worker is an alternative install path with the same behaviour, and a CMS pre-render that stores results at publish time is an optional accelerator, not a requirement.

What activation looks like for a publisher.

  1. Silvr creates the publisher in its config and issues a publishable key bound to the publisher's domains, for example pk_live_example_8f3a. The key is safe to expose: it only works from those origins and is rate-limited.
  2. The publisher adds one tag to the page template or to their tag manager:
    <script async src="https://cdn.silvrshop.com/v1/loader.js" data-silvr-key="pk_live_example_8f3a"></script>
  3. The publisher adds https://api.silvrshop.com to script-src and connect-src in their Content Security Policy, and https://cdn.silvrshop.com to img-src if product thumbnails are served from Silvr.
  4. Silvr switches the key to enabled. The loader fetches its config every 60 seconds at most, so a publisher-side kill switch (enabled: false) takes effect within a minute, with no deploy on either side.

The same tag works on every site. Differences between sites (image URL shapes, video player, section paths, CSS stacking) live in the per-publisher config that the loader downloads, never in the tag.

Where the Silvr proxy sits. One service, six jobs: serve the loader and per-publisher config; accept detection requests from the browser and fetch the images itself; hold the only credential for the recognition service and call it; cache results per image and enforce the publisher's quota; render the result panel as HTML fragments; run the pre-processing worker that warms the cache for new articles and video keyframes. A reader's browser never talks to the recognition service and never sees its key: nothing secret is stored on a client.

2. Finding images and placing the + marker

The browser finds the images and places the markers; Silvr's detector on the proxy finds the items inside each image and Silvr's recognition service names them. Nothing in the browser looks at pixels. The loader sends image URLs (or, for video, one captured frame), the proxy fetches the image, detects the items, recognizes each item, and stores the boxes normalized so they can be mapped onto the image however the page happens to display it.

Step 1. Discovery: which images qualify. The loader walks the document once at start and then relies on observers. An element is a candidate when all of these hold, each threshold a config value:

Ads are never candidates, as images or as video. Ads reach a page three ways and each is excluded. Programmatic ads in iframes (Google Ad Manager and most networks) are invisible to the loader, which cannot see inside another origin's frame. Native and house ads rendered in the page itself (Taboola, Outbrain, sponsored blocks, the publisher's own promotions) are excluded by selector: the defaults cover [data-ad] *, [id^="div-gpt-ad"] *, .ad *, .OUTBRAIN *, [class*="taboola"] *, [class*="sponsored"] * and images wrapped in links to known ad hosts, and each publisher adds its own ad container selectors through the integration checklist. Video ad breaks inside the publisher's player (pre-roll or mid-roll through Google IMA or the player's ad plugin) are skipped: the loader listens for the player's ad-start and ad-end events where the player exposes them, and otherwise does not capture while the element's source differs from the content source. A reader who pauses during an ad sees no affordance. The selectors are verified on your real pages before launch.

Step 2. Lifecycle: knowing when an image appears, changes or leaves. Three browser observers cover every surface in scope:

The loader never sets src itself and never wraps or moves the publisher's elements. It only reads.

Step 3. Canonical key and request. The loader reads currentSrc, which is the variant the browser chose from srcset, and sends it with the element's natural size. The proxy normalizes the URL into an image key by stripping size and quality parameters it knows (for Arc XP resizer URLs: width, height, quality, smart; other resizers are added per publisher), and keys the cache on that plus a rounded aspect ratio. Ten readers on ten screen sizes therefore hit one cache entry for one photo. As a safety net the proxy also indexes results by a perceptual hash of the fetched image, so an unknown resizer still dedupes. A cached result is re-checked once a day with a lightweight request that asks only whether the file changed; if it did, the image is analysed again.

Step 4. Localization happens server-side. The proxy fetches the largest same-aspect variant of the image the reader sees (Arc resizer width raised, Contentful ?w=1600), finds every fashion item in it with Silvr's vision service, and stores each item's box and pin anchor as fractions of the analyzed image. Every item gets its own box and its own recognition, so a photo with a blazer, a bag and boots yields three pins, not one; up to five pins are kept per image. Fetching server-side avoids two browser problems: cross-origin images cannot be read from a canvas unless the publisher's CDN sends CORS headers (the tainted-canvas rule), and a 2 MB photo should not be re-uploaded from every reader's phone. Because the analyzed variant has the same aspect ratio as the displayed one, the normalized boxes map straight onto the element.

Step 5. Mapping a box onto the displayed image. Because detection ran on the same variant the reader sees, the fetched aspect ratio equals the element's natural aspect ratio, and the only transform left is the CSS one. For an element with rect R (getBoundingClientRect), natural size (nw, nh) and a normalized box (bx, by, bw, bh):

Carousel transforms need no special handling because getBoundingClientRect already includes them. When currentSrc changes to a variant with a different crop (a <picture> that switches art direction at a breakpoint), the key changes and the loader requests that variant; the old markers are dropped.

Step 6. Drawing. One host element is appended to document.body, position: fixed, full viewport, pointer-events: none, with a Shadow DOM root so publisher CSS cannot leak in or out. Each tracked image gets a layer inside the host, repositioned on scroll and resize with requestAnimationFrame throttling. Only the affordance, the markers and the panel accept pointer events, so the overlay never blocks a click on the publisher's page or on an ad. Markers sit at each item's pin anchor (the item's centre of mass, falling back to the box centre). The layout is designed for up to five markers on one image: when two anchors fall within 32 px, the later marker shifts along the top edge of its box; every marker keeps a 44 px touch target; and on screens narrower than 480 px with four or more markers, the drawer opens on an "All items" list first so the reader can pick by name instead of by position. The affordance shows the item count, for example "Shop this image, 5", so the reader knows what to expect. The layer's z-index is a config value, default just below the maximum, so it sits above lightboxes and sticky headers. The pill, the markers and the drawer are labelled buttons that work without a mouse: Tab reaches the pill, Enter opens it, the arrow keys move between markers, Escape closes the drawer, and a screen reader announces the item count and each item's name.

Per-surface handling.

SurfaceWhat is differentWhat the loader doesWhat pre-processing adds
Static HTML imagesNothingDiscovery at start, request when within one viewportResults are already cached, so the affordance appears on first paint
Lazy-loaded imagesElement exists but has a placeholder or no source until near the viewport; JavaScript lazy loaders swap data-src into srcWaits for load with a real naturalWidth; keys on the final currentSrc; re-evaluates on src or srcset mutationSame as static; the crawler scrolls the page so lazy images load before extraction
Carousels and slideshowsSlides sit in a transformed track or one <img> swaps its src; some libraries clone the first and last slides; hidden slides may be aria-hiddenTreats each distinct image key once, so clones cost nothing; tracks only slides with intersection above one half; hides markers during the slide transition and redraws on transitionend; cancels stale in-flight requests when a swapped src changes; prefetches the neighbours of the visible slideAll slides warmed, so swiping feels instant
Galleries with a lightboxThumbnails are too small to qualify; the full image is inserted into a fixed, high z-index container on clickMutationObserver picks up the inserted image; the fixed host and configurable z-index keep markers above the lightboxFull-size variants warmed from the gallery's image list
In-page video players (Arc's player, video.js and similar)A <video> element is in the DOM; frames change; the poster is a normal imageShows Shop This Video from the poster; on activation pauses the video, draws the current frame to an offscreen canvas at 1280 px long side, posts the JPEG with the video key and the time rounded to half a second; maps boxes with videoWidth and videoHeight the same way as images; hides markers on seek or playKeyframe extraction server-side (ffmpeg scene cuts plus one frame every two seconds, deduped by perceptual hash) gives the item count and timestamps for a timeline strip, and covers Safari, where native HLS taints the canvas
Third-party iframe players (YouTube, Vimeo)The frame contents are a different origin; nothing inside can be readThumbnail only via oEmbed, or nothingOnly if the publisher provides the source file

Two notes on video. First, frame capture works in Chrome, Firefox and Edge for any player that feeds the element through Media Source Extensions, which is how HLS plays in those browsers; it works in Safari only when the element carries crossorigin="anonymous" and the CDN sends CORS headers, which is a one-attribute change for the publisher. Second, the overlay does not track items across frames while the video plays. Markers appear on a paused frame, which is the same model the editorial demo uses (Shop This Video, a count badge, a timeline). Third, animated images (GIF, animated WebP) follow the video model: the proxy extracts their frames server-side, and the overlay offers a frame picker over a still, so an item that passes by in the animation can still be shopped.

Where pre-processing on page rollout helps. New article URLs come from three sources, in order of lift: polling the publisher's news sitemap or RSS, so new pages are processed within minutes of publication (no publisher work), a server-to-server call from the publisher's CMS on publish (POST /v1/preprocess), and the first reader's visit, which warms the cache for everyone after. The worker renders the page with Playwright, scrolls to trigger lazy loading, extracts candidates with the same rules the loader uses (the rules are one JSON spec shared by both), runs detect and identify, and stores the results. On a pre-processed page the cold path never runs for a reader, hover is instant because identification already happened, and recognition calls spread over the day instead of following traffic spikes. If a publisher grants read access to the Arc XP Content API, the worker can take the story's image list from JSON instead of rendering the page, which also avoids paywall truncation.

3. System architecture

Three parts belong to Silvr: the loader in the page, the Silvr proxy, and the pre-processing worker. The browser only ever talks to Silvr's proxy, and the proxy is the only thing that talks to Silvr's recognition service.

system architecture · 3 new parts, 5 existing, what calls what

The accented boxes are the parts Silvr runs; every arrow is a call, and the only path to the recognition service runs through the proxy.

The page loads the script from the CDN and sends image URLs to the proxy; the proxy fetches the images, calls the recognition service with its credential, and keeps every answer in its cache. The worker warms the same cache from the publisher's feeds before readers arrive.

Request flow for one image.

  1. The reader scrolls. An image enters the one-viewport prefetch zone. The loader waits up to 150 ms to collect neighbours, then sends one POST /v1/detect with up to 10 image URLs.
  2. The proxy checks the quota (token bucket), computes each image key, and looks the key up in the cache. Warm keys return their pins at once.
  3. For a cold key the proxy takes a single-flight lock on the key, answers pending immediately, and finishes in the background: fetch the largest variant, find every fashion item, recognize each one, curate, store. This typically completes within a minute. Nothing is shown to the reader for that image unless they are still on it when the result lands.
  4. The loader shows the affordance for images with at least one pin. On activation it draws the markers at the pin anchors.
  5. Hover or click on a marker sends GET /v1/item/{item_id}. The proxy serves the top match and its alternates from the stored pins, rendered as an HTML fragment; the loader opens the drawer. No live recognition call happens here.
  6. The loader posts interaction events with sendBeacon to POST /v1/events.

Target budgets: affordance visible within 300 ms of an image entering the viewport when warm; drawer open within 400 ms warm. There is no live cold path for readers: a cold image returns pending, the work runs in the background (typically within a minute), and the result is cached for the next reader. Pre-processing from the publisher's sitemap within minutes of publication keeps cold images rare. A cold result that lands while the reader is still on the image is drawn then.

Draft API contract. All browser-facing routes take the publishable key and check the request's Origin against the publisher's registered domains. Responses carry X-Silvr-Quota-Remaining. The generated OpenAPI page is the reference for every route and field.

RouteCalled byPurposeCache
GET /v1/loader.jsPublisher pageThe loader; lazy-loads the overlay and panel modulesCDN, 5 min, then immutable hashed modules
GET /v1/config?key=LoaderPer-publisher policy, selectors, ad exclusions, UI options, tracking level, kill switchCDN, 60 s
POST /v1/detectLoaderBatch of image URLs in, boxes per image outCache per image key, 7 days
GET /v1/image/{image_key}Loader, CDNCached detect result for one image; a CORS simple requestCDN plus cache, 7 days
POST /v1/detect-frameLoaderOne captured video frame (multipart JPEG) with video key and timeCache per video key and half-second, 7 days
GET /v1/item/{item_id}LoaderSKU match, similar items, affiliate links, HTML fragmentCache, 24 h (prices change)
POST /v1/eventsLoaderInteraction events: anonymous counts or full session, per the publisher's choicenone
POST /v1/preprocessPublisher CMS or Silvr's own poller, server to server with a secret keyEnqueue page, image or video URLs for warmingqueue

Example detect request and response:

POST /v1/detect
{
  "key": "pk_live_example_8f3a",
  "page_url": "https://www.example-publisher.com/lifestyle/style/2026/10/09/fall-tailoring/",
  "images": [
    {"id": "c1", "url": "https://www.example-publisher.com/resizer/ABC123.jpg?width=1200", "nw": 1200, "nh": 800},
    {"id": "c2", "url": "https://www.example-publisher.com/resizer/DEF456.jpg?width=960", "nw": 960, "nh": 1200}
  ]
}

200 OK
{
  "results": [
    {"id": "c1", "image_key": "img_7c1e9a", "status": "ok", "items": [
      {"item_id": "it_5f2a", "category": "blazer", "confidence": 0.93, "box": {"x": 0.41, "y": 0.22, "w": 0.18, "h": 0.30}},
      {"item_id": "it_5f2b", "category": "waistcoat", "confidence": 0.88, "box": {"x": 0.44, "y": 0.35, "w": 0.12, "h": 0.20}}
    ]},
    {"id": "c2", "image_key": "img_90aa01", "status": "pending", "retry_after_ms": 800}
  ],
  "quota": {"remaining_minute": 582}
}

Status values: ok, no_items, pending, skipped (quota or allow-list), error. Boxes are fractions of the fetched image, origin top-left. An item response:

GET /v1/item/it_5f2a?key=pk_live_example_8f3a
{
  "item_id": "it_5f2a",
  "match": {"brand": "The Frankie Shop", "title": "Gelso Oversized Blazer - Dark Grey Melange", "price": {"amount": 425, "currency": "USD"}, "image": "https://cdn.silvrshop.com/p/9a1f.jpg", "url": "https://affiliate-redirect.silvr.fashion/out?u=https%3A%2F%2Fthefrankieshop.com%2Fproducts%2Fgelso-blazer&source=overlay_example&page=https%3A%2F%2Fwww.example-publisher.com%2Flifestyle%2Fstyle%2F2026%2F10%2F09%2Ffall-tailoring%2F&img=img_7c1e9a&item=it_5f2a&session=s_4e1c2a", "confidence": 0.91},
  "similar": [{"brand": "...", "title": "...", "price": {"amount": 235, "currency": "USD"}, "image": "...", "url": "https://affiliate-redirect.silvr.fashion/out?u=...&source=overlay_example&img=img_7c1e9a&item=it_5f2b&session=s_4e1c2a"}],
  "html": "<section class=\"silvr-item\">...</section>"
}

Every url goes through Silvr's affiliate redirect. The page, img, item and session parameters are stored with the click by Silvr's affiliate redirect, so clicks and conversions report per article and per image; under counts tracking only source is sent.

The drawer ends with an affiliate disclosure line: the publisher's own wording when they provide one, Silvr's default line otherwise.

The publisher config the loader downloads:

{
  "publisher": "example", "enabled": true,
  "trigger": "viewport", "batch_window_ms": 150, "prefetch_viewports": 1, "max_batch": 10, "max_images_per_page": 40, "min_rendered_px": 200,
  "selectors": {"include": ["article img", "figure img", ".gallery img", "video"], "exclude": ["header *", "footer *", "nav *"]},
  "ads": {"exclude": ["[data-ad] *", "[id^=\"div-gpt-ad\"] *", ".ad *", ".OUTBRAIN *", "[class*=\"taboola\"] *", "[class*=\"sponsored\"] *", ".wp-ad *"]},
  "path_allowlist": ["/lifestyle/", "/style/"],
  "path_denylist": ["/lifestyle/2026/10/08/some-sensitive-story/", "*/live-updates/*"],
  "video": {"mode": "pause_to_shop", "ad_events": "ima"},
  "ui": {"affordance_label": "Shop this image", "panel": "drawer", "z_index": 2147483000},
  "quota": {"per_minute": 600, "per_day": 200000},
  "events": "full",
  "consent": {"wait_for": "tcf"},
  "version": "2026-10-10T22:00:00Z"
}

An events call under full tracking. Under counts the same call carries only t, image_key and item_id, with no session and no page URL.

POST /v1/events
{
  "key": "pk_live_example_8f3a",
  "session": "s_4e1c2a",
  "page_url": "https://www.example-publisher.com/lifestyle/style/2026/10/09/fall-tailoring/",
  "events": [
    {"t": "affordance_shown", "image_key": "img_7c1e9a", "ts": 1760133600123},
    {"t": "activated", "image_key": "img_7c1e9a", "ts": 1760133602410},
    {"t": "marker_click", "image_key": "img_7c1e9a", "item_id": "it_5f2a", "ts": 1760133603902},
    {"t": "outbound_click", "image_key": "img_7c1e9a", "item_id": "it_5f2a", "ts": 1760133607550}
  ]
}

Where the per-page request policy, batching, caching and quotas sit. The policy is not settled, so every knob is a config value with a default, enforced in one place:

KnobDefaultEnforced inEffect
triggerviewportLoaderpage sends all candidates at load; manual sends on hover
batch_window_ms, max_batch150 ms, 10LoaderCoalesces neighbouring images into one request
prefetch_viewports1LoaderHow far ahead of the reader to ask
max_images_per_page40Loader and proxyHard cap per page view
ads.excludeDefault ad selectors plus the publisher's ownLoader and workerAds are never candidates, as images or video
path_allowlist, path_denylist, page meta opt-outempty, empty, noneLoader and workerSection scoping and single-page exclusions for sensitive content
Detect cache TTL7 daysProxy cache, CDN for GET /v1/imagePer image key plus perceptual-hash index
Identify cache TTL24 hProxyPrices and availability change
Negative cache for no_items24 hProxyNon-fashion photos are not re-sent
quota.per_minute, quota.per_day600, 200,000Proxy (token bucket and counter)429 with Retry-After; loader goes quiet for the window
eventsfull recommended; counts availableLoader and proxyFull adds session, page, image and item to events and to outbound links
consent.wait_fornoneLoaderUnder full, send nothing until the publisher's consent signal fires
enabledtrueLoaderKill switch, effective within 60 s
consent.honor_gpctrueLoader and proxyA reader's Global Privacy Control signal drops that session to counts; events carry the reason

Where integration friction sits.

Synchronization and latency challenges.

ChallengeWhere it showsHandling
src swaps while a request is in flight (slideshow)LoaderEach request is tagged with element and URL; stale results are dropped; AbortController cancels the fetch
Layout changes after results arriveLoaderMarker positions are computed from the live rect at draw time, never stored
Scroll jankLoaderPassive listeners, requestAnimationFrame, transforms only, no layout reads inside the scroll handler
Carousel mid-transitionLoaderMarkers hidden during the transform transition; redrawn on transitionend or after 600 ms
Placeholder versus real imageLoaderCandidates must have naturalWidth above the threshold and a network URL; re-evaluated on load
Video time versus the frame that was sentLoaderThe frame carries its time; markers are tied to it and hidden on seek or play
Cache stampede on a new articleProxySingle-flight lock per image key in the cache; followers get pending or wait
Vision service slow or downProxy and loaderBackground completion with one retry, pending with retry_after_ms; after repeated failures a circuit breaker hides affordances for 60 s
Several proxy workersProxyAll shared state in the shared cache; only an LRU of hot results in process
Config changeLoader60 s cache on config; no deploy on either side

4. Publisher integration checklist

A publisher engineer can complete the install in under an hour, and nothing on this list needs a code release if a tag manager is in use.

Before install, send Silvr:

Install:

What the script does and does not do, for your review:

Your controls, all without a deploy: a kill switch effective within a minute; the section allow-list and the page deny list; the per-image and per-page opt-outs; the tracking level; quotas per minute and per day; the z-index and panel style; the affordance label.

Optional accelerators, each one line of work on your side: a server-to-server POST /v1/preprocess from your CMS on publish, so results are warm before the first reader; read access to your Arc XP Content API for a Silvr service account, so warming needs no page rendering; the crossorigin attribute on video elements.

5. Edge-case register

Each row is a case the overlay meets on the surfaces in scope, what happens, and whether it is included today.

Surface or caseBehaviourAvailability
Image with shortest rendered side under 200 px, or aspect ratio beyond 3:1IgnoredIncluded
Blurred low-quality placeholder before the real imageWait for load with a real naturalWidth, then evaluateIncluded
src changes after detection (a slideshow reusing one <img>)New key; old markers dropped; in-flight request cancelledIncluded
Same photo at several resizer widths across readersOne cache entry (normalized key plus perceptual hash)Included
<picture> switches to a different crop at a breakpointRe-detect on the new currentSrc; cache keyed with the aspect bucketIncluded
object-fit: cover hides part of the imageMapping accounts for the drawn region; markers outside it are hiddenIncluded
Carousel clone slides (first and last duplicated)Deduped by image key; no extra recognition callsIncluded
Slide mid-transitionMarkers hidden; redrawn on transitionend or after 600 msIncluded
Gallery thumbnail grid with a lightboxThumbnails ignored; the full image is handled when insertedIncluded
Lightbox or sticky header with a very high z-indexOverlay z-index is configurable and defaults just below the maximumIncluded
Image inside an ad iframeInvisible to the loader; untouchedIncluded
Image matching an exclude selector (header, nav, footer, ads)SkippedIncluded
Photo with no fashion itemsno_items, negative-cached 24 h, no affordanceIncluded
Two items overlapping (a jacket over a shirt)Second marker shifts to the top edge of its box; 32 px minimum spacingIncluded
More than 40 candidates on one pageFirst 40 in viewport order; the rest skippedIncluded
Quota exhausted429 with Retry-After; loader goes quiet; no reader-visible errorIncluded
Recognition service timeout or errorpending with one retry, then silent for that image; circuit breaker after repeated failuresIncluded
Page not in the publisher's path allow-listLoader exits after reading configIncluded
Per-image opt-out data-silvr="off"SkippedIncluded
Client-side navigation or back/forward cache restoreMutationObserver picks up new content; page_url read from location; re-attach on pageshowIncluded
Touch deviceTap replaces hover; panel opens as a bottom sheetIncluded
Reader prefers reduced motionNo dim or slide animationIncluded
Video poster before playTreated as an image with a Shop This Video labelPlanned
In-page video paused by the reader or by activationCurrent frame captured at 1280 px and detected; markers tied to that timePlanned
Video playingNo markers while playingPlanned
Safari with native HLS (tainted canvas)Needs crossorigin="anonymous" on the element; otherwise poster onlyPlanned
Server-side keyframes with a timeline strip and item countWorker extracts and detects keyframes for publisher-hosted filesLater
Third-party iframe player (YouTube, Vimeo)Thumbnail at most; frames cannot be readNot supported
CSS background-image photosNot detectedLater
Consent platform classifies Silvr as a vendorLoader waits for the consent signal before sending anythingPlanned
Wire-service photo licensed for editorial use onlyPublisher excludes by section or per image; policy is the publisher'sIncluded
Publisher CMS pre-render (Arc XP block storing boxes at publish time)Zero recognition calls at read timeLater
AMP pages, native apps, email, reader modeNothing injectedNot supported
Display ad as image or video (ad iframe, native ad unit, house promotion, in-player ad break)Never a candidate: iframe isolation, ad selectors, and the player's ad-start and ad-end eventsIncluded
Page excluded by the publisher's deny list or a page-level meta tagLoader exits after reading config and the page headIncluded
Publisher chooses full session trackingNothing sent until the consent signal fires; outbound links carry page, image, item and session into the affiliate redirectIncluded
Image not yet in the cache (cold)No affordance; the proxy answers pending, runs detection and recognition in the background (typically within a minute), and caches the result for the next readerIncluded
Reader's browser sends Global Privacy ControlTreated as counts for that session even under full tracking; the events record the reasonIncluded
Publisher's own commerce modules (roundups with their own affiliate links)Shoppable by default; excluded only when the publisher names the module selectors, like the page and image opt-outsIncluded
Photo replaced at the same URLCached result re-checked once a day with a lightweight request; analysed again only if the file changedIncluded
Keyboard-only reader or screen readerPill, markers and drawer are labelled buttons: Tab, Enter, the arrow keys, EscapeIncluded
Article behind a hard paywall (warming)Worker uses the publisher's crawler access or content API when granted; otherwise the page's structured data, which covers hero photos but not galleriesPlanned
Two boxes on the same item resolving to the same productMerged into one pin; two people wearing the same coat stay two pinsIncluded
Pinch-zoom on a phoneMarkers follow the zoomed viewIncluded
Printing the pageOverlay hiddenIncluded
Animated image (GIF, animated WebP)Follows the video model: frames extracted server-side, a frame picker over a stillPlanned
Image wrapped in a linkThe pill and markers sit in Silvr's own layer, so a click on them never follows the publisher's link; a click on the photo itself still doesIncluded
Browser asks for reduced dataSame one-screen prefetch of pins; drawer thumbnails load only on activationIncluded
Affiliate disclosure in the drawerPublisher's own wording when provided, Silvr's default line otherwiseIncluded