← All posts

AEOsim blog

Where your banners sit decides whether AI retrieval finds your product facts: The geometry of page chrome

Table of contents

The problem

A buyer asks an AI engine for a cabin bag under 2.5 kg. Behind the answer, a retriever scores chunks of candidate pages against that request. Suppose your bag's weight sits in a chunk that also holds a festive-sale banner, a returns widget and a "customers also viewed" carousel. That chunk is now partly about those things. This post calls that material page chrome.

This post puts a number on what chrome costs. Production engines don't publish their retrievers, so the measurements use open embedding models and an open reranker as a retrieval proxy. They are labelled that way throughout, and they say nothing direct about any production engine.

The study has two main results. First, chrome placed above the product facts hurts retrieval far more than the same chrome placed below them. Second, a chunk's similarity score can stay high while its rank among competing chunks falls.

Chrome placed before the product facts

Many pipelines read only the first N tokens or the first few chunks of a page. If a page opens with B tokens of chrome and facts are spread evenly through a product block of L tokens, the share of facts inside the window is:

S = min(1, max(0, N − BL))

Once the window binds, every chrome token at the top costs 1/L of the facts. Chrome at the bottom of the page leaves the window alone.

Real pages often open this way. On the study pages, the share of the first 512 tokens labelled chrome in source order ran from 0.250 to 1.000. Some product pages open entirely in navigation, banners and footer text.

To test placement, each page's own extracted chrome was moved to the top or the bottom of the page, and the first N tokens were read. The table shows bge-m3 at the embedding stage with 512-token chunks. Recall is 0 when the constraint fact falls outside the window.

WindowChrome first: recall@5Facts inside the windowChrome last: recall@5Facts inside the windowBottom minus top
5120.2980.3330.8450.9640.548
1,0240.5360.7860.8451.0000.310
2,0480.6310.9760.8451.0000.214
No truncation0.6431.0000.8451.000n/a

At a 512-token window, recall@5 is 0.298 (95% CI 0.143–0.464) with chrome first and 0.845 (95% CI 0.750–0.940) with chrome last. When chrome leads, only 0.333 of the constraint facts fit in the window at all. The gap narrows as the window grows. With no truncation every fact is in the window, and chrome-first pages still reach only 0.643 against 0.845. Chrome at the top changes the first chunks even when nothing is cut off. multilingual-e5-large and all-mpnet-base-v2 show the same pattern in Figure 1. A separate check with short buyer-style queries found the same split at a 512-token window. There bge-m3 recall@5 was 0.250 with chrome first and 0.786 with chrome last.

Recall@5 of the constraint fact by truncation window, with each page's own chrome placed first or last, for bge-m3, multilingual-e5-large, and all-mpnet-base-v2
Figure 1. Recall@5 of the constraint fact by truncation window, with each page's own chrome placed first (dashed) or last (solid), for three embedding models, embedding stage, 512-token chunks. Error bars are 95% cluster-bootstrap intervals. Retrieval proxy, not a live AI engine.

Chrome share, similarity and rank

The naive model

Embedding models map text to unit vectors, and retrieval ranks chunks by cosine similarity to the query. A simple model treats a chunk as a mix of product text with direction p and chrome with direction c, where chrome carries weight α:

e(α) = (1 − α)p + αc∥(1 − α)p + αc∥

Its similarity to a query q, with p and c unit vectors:

cos(q, e) = (1 − α)(q · p) + α(q · c)√((1 − α)² + α² + 2α(1 − α)(p · c))

The simplest case assumes chrome is unrelated to both the query and the product text, so q·c ≈ 0 and p·c ≈ 0. Call this the orthogonal baseline. The effect then reduces to a retention factor that depends on α alone:

r(α) = cos(q, e)cos(q, p) = 1 − α√((1 − α)² + α²) = cos(arctan α1 − α)

The table below is the orthogonal baseline, computed from that formula. None of its values come from the page experiment.

Chrome weight αRotation away from productRetention r(α)Similarity lost
0.16°0.9941%
0.214°0.9703%
0.323°0.9198%
0.434°0.83217%
0.545°0.70729%
0.656°0.55545%

Cosine is flat near zero angle, so a small orthogonal component barely turns the vector. Under this baseline, chrome is close to free below a fifth of a chunk and expensive past a third.

What the models did

Measured retention is much gentler than the baseline. At a chrome token share of 0.4, bge-m3 keeps 0.940 of the clean cosine (95% CI 0.925–0.956), where the baseline predicts 0.832. At the same share, multilingual-e5-large keeps 0.985 and all-mpnet-base-v2 keeps 0.944. At 0.2, bge-m3 keeps 0.964, close to the baseline's 0.970.

Two things account for the difference. First, chrome is not orthogonal to product text. Real embedding spaces are not centred, so unrelated texts still score a positive cosine, and q·c and p·c sit well above zero for all three models. Second, pooling weights tokens unevenly, and chrome is often short, repetitive and low in information. A fit of the form odds(αeff) = k · odds(αtoken) gives k below 1 for every model, so chrome's effective weight sits below its token share. For bge-m3, a token share of 0.4 behaves like a mixture weight near 0.2. The fitted values are in the Methods appendix.

Similarity retention against chrome token share for bge-m3, multilingual-e5-large, and all-mpnet-base-v2, compared with the orthogonal baseline
Figure 2. Similarity retention against chrome token share, constraint queries, 512-token chunks. Points are observed retention with 95% cluster-bootstrap intervals. The solid curve is the orthogonal baseline r(α), drawn as if token share were the mixture weight. Dashed curves are the fitted r(αeff). Retrieval proxy, not a live AI engine.

Why rank falls

A retriever returns its top-k chunks, so yours is read only if it beats the alternatives. With clean similarities syou and scomp and chrome weights α and β, your chunk stays ahead when:

r(α) syou > r(β) scomp

The proxy measured rank against other brand product pages in the same category. For bge-m3 on constraint queries with 512-token chunks, recall@5 is 0.881 with clean chunks (95% CI 0.762–0.976). It falls to 0.774 at token share 0.2, 0.690 at 0.4 and 0.440 at 0.6 (95% CI 0.285–0.619). At 0.4 the chunk still holds 0.940 of its clean cosine.

The likely mechanism is crowding. Chunks from similar products in one category score close to each other against the same query, so a small shift in similarity can be enough to change their order. The positive cosine between unrelated texts lifts competitors as well, so it does little to protect rank. This run did not measure the margin between the gold chunk and the runner-up, so this explanation is a reasonable reading of the results and has not been tested directly.

A cross-encoder reranker reads the query and chunk together and can attend to the one relevant sentence, so dilution should hurt it less. Reordering bge-m3's top 50 at token share 0.6, the reranker reaches a recall@5 of 0.726 (95% CI 0.583–0.845), up from 0.440. That is still below the clean embedding recall of 0.881. The same reranker lifts mpnet from 0.476 to 0.726. Its own score also drops as chrome grows, by 0.078 at token share 0.4 and 0.123 at 0.6.

Recall@5 against chrome token share for three embedding models and a reranker over each model's top 50
Figure 3. Recall@5 against chrome token share, constraint queries, 512-token chunks, for the three embedding models (solid) and the reranker over each model's top 50 (dashed). Error bars are 95% cluster-bootstrap intervals. Retrieval proxy, not a live AI engine.

The inequality matters most when clean similarities are nearly equal, as they would be for your own page and a marketplace listing of the same product. Brand-versus-marketplace ranking is untested here and is the next experiment.

What to change on a product page

  • Keep each constraint fact (price, weight, size, warranty, delivery) in a block that holds only product text.
  • Put banners, policy widgets and carousels after the product facts in the page source. Text extractors usually read source order, so reordering with CSS doesn't help.
  • Don't repeat the same promo line in every section. Repetition raises chrome's weight in every chunk.
  • Test a change with paired queries before rolling it out across the catalogue.

The placement result above supports moving chrome below the facts. The points about CSS and repeated promos follow from how extractors work and from the mixture model. This experiment did not measure a lift from either change.

Limits

  • This is a retrieval proxy on open models. Production chunkers, embedders and rerankers are unknown and may behave differently.
  • The main constraint queries were revised once. The first set were short buyer-style phrases, often shared across sibling SKUs, and reached only 0.524 clean recall@5 (bge-m3, 512-token chunks). They were rewritten as longer unbranded excerpts of the text around the fact sentence, copied from the gold chunk with brand and product names removed and set after a short "I need a … that meets this requirement" frame. That raises lexical overlap with the gold chunk. A separate check then used 84 new short buyer-style queries of 3 to 8 words, tied to the same facts and gold chunks. For bge-m3 at a 512-token window, clean recall@5 was 0.786 and chrome-first recall@5 was 0.250, so the placement effect holds with short buyer queries. That check used small candidate pools of 43 to 49 chunks, so the gap between placements is more telling than the absolute levels. The token-share sweep was not rerun with short queries.
  • Both query sets were drafted by an agent and have not had human review.
  • In the token-share sweep, chrome was injected synthetically at the chunk level, using banner, footer and policy lines from other brands' pages. The placement test moved each page's own chrome, extracted from the live HTML. Real chrome also interacts with rendering and layout.
  • The inverter study pages are concentrated in one brand, Luminous, plus one Genus combo. The pool is 34 pages, smaller than a catalogue-scale index.
  • Retrieval is necessary for a page to be picked and does not guarantee it. A chunk that is read can still be passed over.

Appendix: Methods

Pages. The proxy used public Indian D2C product pages in three categories: cabin luggage, tea, and home inverters. Thirty-six URLs were fetched and thirty-four returned a product page. Twenty-one of those are the study set, seven in each category. The other thirteen sit in the pool as competitor product pages. V-Guard product URLs in the sitemap returned HTTP 404, which is why the inverter set leans on Luminous. No product fact was edited. Snapshots are timestamped in IST.

Chunking and chrome. Tokens for chunking, token share and truncation are cl100k_base counts. Chunkers are 256 and 512 tokens with about 10% overlap, packed on sentence boundaries so a fact sentence is never split. Injected chrome targets token shares of 0, 0.1, 0.2, 0.3, 0.4, 0.5 and 0.6. Achieved shares landed within a few thousandths of those targets on average. Every variant kept the fact sentences of its chunk verbatim, and every page variant still contained every fact sentence from the clean product text.

Queries. Each study product has 10 unbranded queries (4 constraint, 3 use-case, 3 comparison) and 2 exact product-name queries. The gold chunk is marked only for constraint queries. The revised set had to clear a clean recall@5 of 0.6 on at least one model. The highest clean recall@5 on the revised set is 0.952 for multilingual-e5-large at 512 tokens (95% CI 0.881–1.000).

Models. BAAI/bge-m3 (no prefix; revision 5617a9f61b028005a4858fdac845db406aefb181), intfloat/multilingual-e5-large (query: / passage:; revision 3d7cfbdacd47fdda877c5cd8a79fbcc4f2a574f3) and sentence-transformers/all-mpnet-base-v2 (no prefix; revision e8c3b32edf5434bc2275fc9bab85f82640a19130). The reranker is BAAI/bge-reranker-v2-m3 (revision 953dc6f6f85a1b2dbfca4c34a2796e7dde08d41e), over the embedding top 50, with a 512-token cap. Library versions: torch 2.14.1+cu130, sentence-transformers 6.2.0, transformers 5.20.2. The GPU was a Tesla T4, and models ran one at a time. e5 truncated 297 texts at 512 tokens and mpnet truncated 404 at 384. bge-m3's context is 8192, so it still read the 22 texts that were longer than 1024 tokens.

Statistics. Comparisons are paired on the same query and product. Intervals are 95% intervals from a cluster bootstrap that resamples products 2000 times, so query-product rows are not treated as independent. The seed is 20261021.

Intervals not shown in the body. All are bge-m3, constraint queries, 512-token chunks unless noted. Placement, chrome first: 1,024 tokens 0.536 (0.369–0.690); 2,048 tokens 0.631 (0.476–0.774); no truncation 0.643 (0.488–0.774). Placement, chrome last: 0.845 (0.726–0.952) at 1,024 tokens, 2,048 tokens and no truncation. Placement, paired bottom minus top: 0.548 (0.381–0.702) at 512; 0.310 (0.155–0.476) at 1,024; 0.214 (0.083–0.369) at 2,048. multilingual-e5-large at 512: 0.595 (0.417–0.762). all-mpnet-base-v2 at 2,048: 0.143 (−0.024–0.310). Retention: bge-m3 0.964 at 0.2 (0.950–0.979); multilingual-e5-large 0.985 at 0.4 (0.978–0.992); all-mpnet-base-v2 0.944 at 0.4 (0.903–0.985). Recall@5 by token share: 0.774 at 0.2 (0.655–0.869); 0.690 at 0.4 (0.560–0.810). Reranker over all-mpnet-base-v2 at 0.6: embedding 0.476 (0.310–0.643), reranker 0.726 (0.595–0.845). Reranker score change, score(variant) − score(clean): −0.078 at 0.4 (−0.152 to −0.014); −0.123 at 0.6 (−0.215 to −0.035).

Orthogonality checks. Mean q·c and p·c on the injected chrome at the 512-token chunker: bge-m3 0.488 (0.461–0.513) and 0.478 (0.449–0.502); multilingual-e5-large 0.799 (0.792–0.807) and 0.813 (0.806–0.819); all-mpnet-base-v2 0.314 (0.256–0.373) and 0.346 (0.296–0.402).

Fitted mapping. odds(αeff) = k · odds(αtoken), fitted by least squares of observed retention against r(αeff) on constraint queries. At the 512-token chunker, k is 0.375 for bge-m3 (0.334–0.414), 0.157 for multilingual-e5-large (0.128–0.183) and 0.304 for all-mpnet-base-v2 (0.192–0.399).

Short-query check

This is a separate run. It used 84 short buyer-style constraint queries, 4 per study product, 3 to 8 words each. Each is paired with one of the main constraint queries and uses the same fact and gold chunk. They are unbranded, agent-drafted and pending human review. The embedder is bge-m3 and the reranker reorders its top 50, with the same models, revisions and bootstrap as the main run. Chunks are 512 tokens and the window is the first 512 tokens. The chrome-first and chrome-last pools are the main run's placement pools, 49 chunks each. The clean pool has 43 chunks, with the study page's own chrome removed. Every cell has nproducts = 21 and nqueries = 84. Intervals are 95% cluster-bootstrap intervals.

PlacementFacts inside the windowbge-m3 recall@5Reranker recall@5
Clean0.9640.786 (0.655–0.893)0.893 (0.810–0.964)
Chrome first0.3330.250 (0.119–0.393)0.286 (0.143–0.440)
Chrome last0.9640.786 (0.667–0.881)0.881 (0.786–0.952)

Under chrome-first placement only about a third of the constraint facts remain inside the first 512 tokens, so most of this gap is window loss. Pools this small make recall@5 easier than in a real index, so compare placements rather than absolute levels.

References

Vignesh Kanike is Founding Engineer at AEOsim, working on measurement methods for what AI retrieval actually reads on a product page. Related reading: Visibility Has a Third Axis, Visibility Has Two Axes, and Clarification as a Branch Point.

To see which chunk of your product page an AI engine reads, and what is diluting it, book a free mini audit.

Vignesh Kanike