diff --git a/docs/superpowers/specs/2026-04-15-server-side-ad-templates-design.md b/docs/superpowers/specs/2026-04-15-server-side-ad-templates-design.md
new file mode 100644
index 00000000..454f3764
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-15-server-side-ad-templates-design.md
@@ -0,0 +1,363 @@
+# Server-Side Ad Templates Design
+
+*April 2026*
+
+---
+
+## 1. Problem Statement
+
+Today's display ad pipeline on most publisher sites is structurally sequential
+and browser-bound:
+
+1. Page HTML arrives at browser
+2. Prebid.js (~300KB) downloads and parses
+3. Smart Slots SDK scans the DOM to discover ad placements
+4. `addAdUnits()` registers slot definitions
+5. Prebid auction fires from the browser (~80–150ms RTT to SSPs)
+6. Bids return (~1,000–1,500ms window)
+7. GPT `setTargeting()` + `refresh()` fires
+8. GAM creative renders
+
+**Total time to ad visible: ~3,100ms.**
+
+The browser is the slowest possible place to run an auction. It must first download and parse
+multiple SDKs, scan the DOM to discover what ad slots exist, and then fire SSP requests over
+a consumer internet connection with high and variable latency.
+
+Trusted Server sits at the Fastly edge — milliseconds from the user, with data-center-to-data-center
+RTT to Prebid Server (~20–30ms vs ~80–150ms from a browser). The server knows, from the request
+URL alone, exactly which ad slots are available on any given page. There is no reason to wait for
+the browser.
+
+---
+
+## 2. Goal
+
+Enable Trusted Server to:
+
+1. Match an incoming page request URL against a set of pre-configured slot templates
+2. Immediately fire the full server-side auction (all providers: PBS, APS, future wrappers) in
+ parallel with the origin HTML fetch — before the browser receives a single byte
+3. Inject GPT slot definitions into `
` so the client can define slots without any SDK
+4. Return pre-collected winning bids to the browser's lightweight `/auction` POST before the
+ browser would have even finished parsing Prebid.js
+5. Eliminate Prebid.js from the client entirely
+
+**Target time to ad visible: ~1,200ms. Net saving: ~2,000ms.**
+
+---
+
+## 3. Non-Goals
+
+- Eliminating client-side GPT / Google Ad Manager — GAM remains in the rendering pipeline
+ for Phase 1. The GAM call (`securepubads.g.doubleclick.net`) moves server-side in a future phase.
+- Dynamic slot discovery (reading the DOM) — this design commits to pre-defined, URL-matched
+ slot templates. Smart Slots' dynamic injection behavior is replaced by server knowledge.
+- Changing the `AuctionOrchestrator` internally — the orchestrator already handles parallel
+ provider fan-out. This design adds a new trigger point, not new auction logic.
+
+---
+
+## 4. Architecture
+
+### 4.1 New File: `creative-opportunities.toml`
+
+A new config file at the repo root, alongside `trusted-server.toml`. It holds all slot templates:
+page pattern matching rules, ad formats, floor prices, and GAM targeting key-values. Bidder-level
+params (placement IDs, account IDs) live in Prebid Server stored requests, keyed by slot ID — not
+in this file.
+
+Loaded at build time via `include_str!()`, parsed into `Vec` at startup.
+Ad ops can edit this file independently of server configuration.
+
+`floor_price` is the publisher-owned hard floor per slot — the source of truth for the minimum
+acceptable bid price, enforced at the edge before bids reach the ad server. Any bid below the
+floor is discarded at the orchestrator level before it enters `__ts_bids`. SSPs may apply their
+own dynamic floors independently within their platforms; this floor is the publisher's baseline
+that supersedes all other floor logic by virtue of being enforced earliest in the pipeline.
+
+**Schema:**
+
+```toml
+[[slot]]
+id = "atf_sidebar_ad"
+page_patterns = ["/20*/"]
+formats = [{ width = 300, height = 250 }]
+floor_price = 0.50
+
+[slot.targeting]
+pos = "atf"
+zone = "atfSidebar"
+
+[[slot]]
+id = "below-content-ad"
+page_patterns = ["/20*/"]
+formats = [{ width = 300, height = 250 }, { width = 728, height = 90 }]
+floor_price = 0.25
+
+[slot.targeting]
+pos = "btf"
+zone = "belowContent"
+
+[[slot]]
+id = "ad-homepage-0"
+page_patterns = ["/", "/index.html"]
+formats = [{ width = 970, height = 250 }, { width = 728, height = 90 }]
+floor_price = 1.00
+
+[slot.targeting]
+pos = "atf"
+zone = "homepage"
+slot_index = "0"
+```
+
+**Rust type:**
+
+```rust
+#[derive(Debug, Clone, serde::Deserialize)]
+pub struct CreativeOpportunitySlot {
+ pub id: String,
+ pub page_patterns: Vec,
+ pub formats: Vec,
+ pub floor_price: Option,
+ pub targeting: HashMap,
+}
+```
+
+### 4.2 URL Pattern Matching
+
+At request time, TS matches the request path against each slot's `page_patterns`. Patterns are
+glob-style strings:
+
+- `/20*/` — matches all date-prefixed article paths (e.g., `/2024/01/my-article/`)
+- `/` — matches the homepage exactly
+- `/index.html` — exact match
+
+Multiple slots can match a single URL. All matching slots are collected and fed into a single
+auction as separate impressions. Pattern matching is purely in-memory against the pre-parsed
+config — sub-millisecond.
+
+### 4.3 Auction Trigger
+
+When slots are matched, TS immediately calls `AuctionOrchestrator::run_auction()` with the
+matched slots converted to `AdSlot` objects. This happens at request receipt time — in parallel
+with the origin fetch.
+
+The orchestrator's existing behaviour is unchanged:
+- All providers (PBS, APS, any configured wrappers) are dispatched simultaneously
+- Per-provider timeout budgets are enforced from the remaining auction deadline
+- Floor price filtering, bid unification, and winning bid selection are applied as today
+- PBS resolves bidder params from its stored requests by slot ID — no bidder params travel
+ through TS or the browser
+
+**On NextJS 14 (buffered mode):** TS must buffer the full origin response before forwarding.
+This gives the auction the entire origin response time (~150–400ms typical) to run before
+any HTML is forwarded. In practice, bids are often collected before origin even responds.
+
+**On NextJS 16 (streaming mode):** TS streams HTML chunks to the browser immediately. The
+auction runs in parallel. Bid injection into `` must complete before the `` tag
+is forwarded. If the auction has not returned by the time `` is encountered, TS waits
+up to the remaining auction budget, then flushes with whatever bids have arrived (partial
+results) or no targeting if timed out. Content after `` is never held.
+
+### 4.4 Head Injection
+
+TS injects two separate `