diff --git a/docs/audit/theming/README.md b/docs/audit/theming/README.md
index abf67f048..83af524cc 100644
--- a/docs/audit/theming/README.md
+++ b/docs/audit/theming/README.md
@@ -10,9 +10,9 @@
- **Medido**: 2026-08-21 · **162 recetas** con CSS + **8 componentes sin receta** = 170 fichas, el árbol entero de `eidos/components/`
- **La pregunta**: ¿cuánto de la apariencia de cada componente puede cambiar un tema **sin tocar el sistema ni la receta**?
-- **Alcance global**: **50%** — 2323 de 4676 knobs pasan por un token público del componente
-- **Reparto**: público 2323 · privado 627 · global 1160 · literal 566 · sistema transversal 509 _(fuera del ratio)_
-- **Sin token público propio**: 32 · **alcance < 20 %**: 25 · **alcance 100 %**: 10 · **con eje `size`**: 59
+- **Alcance global**: **50%** — 2326 de 4676 knobs pasan por un token público del componente
+- **Reparto**: público 2326 · privado 627 · global 1157 · literal 566 · sistema transversal 509 _(fuera del ratio)_
+- **Sin token público propio**: 31 · **alcance < 20 %**: 24 · **alcance 100 %**: 10 · **con eje `size`**: 59
## Cómo se lee
@@ -91,12 +91,12 @@ theming §6.7 lo pone delante (`hover-bg`). Las fichas proponen la forma
| 19 | [timeline](./timeline.md) | 24 | 51% | 49 | 45 |
| 20 | [avatar](./avatar.md) | 23 | 36% | 36 | 0 |
-## Sin ningún token público propio (32)
+## Sin ningún token público propio (31)
Ni una clave pública en `lib/recipes/base.ts` — alguno tiene privados forward
(`_palette-*`), que no son contrato: un tema no puede nombrarlos.
-[`alert-dialog`](./alert-dialog.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`scroll-frames`](./scroll-frames.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
+[`alert-dialog`](./alert-dialog.md) · [`aspect-ratio`](./aspect-ratio.md) · [`auto-grid`](./auto-grid.md) · [`avatar`](./avatar.md) · [`cascade`](./cascade.md) · [`css-field`](./css-field.md) · [`date-picker`](./date-picker.md) · [`display`](./display.md) · [`field-langs`](./field-langs.md) · [`heading`](./heading.md) · [`highlight`](./highlight.md) · [`knob`](./knob.md) · [`mark`](./mark.md) · [`mask-field`](./mask-field.md) · [`menu-dial`](./menu-dial.md) · [`mockup`](./mockup.md) · [`month-grid`](./month-grid.md) · [`motion`](./motion.md) · [`number-field`](./number-field.md) · [`palabras`](./palabras.md) · [`range-calendar`](./range-calendar.md) · [`skin-media-player`](./skin-media-player.md) · [`stack`](./stack.md) · [`text`](./text.md) · [`text-blur`](./text-blur.md) · [`text-circular`](./text-circular.md) · [`text-focus`](./text-focus.md) · [`text-gradient`](./text-gradient.md) · [`text-scramble`](./text-scramble.md) · [`wrap`](./wrap.md) · [`year-grid`](./year-grid.md)
## Tabla completa (162 recetas, por alcance ascendente)
@@ -109,7 +109,6 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [year-grid](./year-grid.md) | 0% | 61 | 0 | 7 | 0 | 5 | 49 | 0 | – |
| [field-langs](./field-langs.md) | 0% | 41 | 0 | 0 | 31 | 8 | 2 | 0 | – |
| [skin-media-player](./skin-media-player.md) | 0% | 11 | 0 | 0 | 2 | 7 | 2 | 0 | y |
-| [scroll-frames](./scroll-frames.md) | 0% | 9 | 0 | 1 | 4 | 4 | 0 | 0 | – |
| [menu-dial](./menu-dial.md) | 0% | 8 | 0 | 3 | 1 | 4 | 0 | 0 | y |
| [mark](./mark.md) | 0% | 5 | 0 | 2 | 1 | 2 | 0 | 0 | – |
| [aspect-ratio](./aspect-ratio.md) | 0% | 3 | 0 | 0 | 1 | 2 | 0 | 0 | – |
@@ -145,6 +144,7 @@ La columna «contrato» cuenta las claves **públicas** del bloque del component
| [search-field](./search-field.md) | 31% | 14 | 4 | 5 | 0 | 4 | 1 | 17 | y |
| [password-field](./password-field.md) | 31% | 36 | 11 | 5 | 9 | 10 | 1 | 23 | y |
| [aura](./aura.md) | 31% | 35 | 11 | 4 | 14 | 6 | 0 | 24 | y |
+| [scroll-frames](./scroll-frames.md) | 33% | 9 | 3 | 1 | 1 | 4 | 0 | 4 | – |
| [toggle-group](./toggle-group.md) | 33% | 3 | 1 | 0 | 1 | 1 | 0 | 1 | – |
| [background](./background.md) | 35% | 26 | 8 | 6 | 6 | 3 | 3 | 31 | – |
| [avatar](./avatar.md) | 36% | 36 | 13 | 17 | 0 | 6 | 0 | 0 | y |
diff --git a/docs/audit/theming/scroll-frames.md b/docs/audit/theming/scroll-frames.md
index ca7c2ada9..f958d690f 100644
--- a/docs/audit/theming/scroll-frames.md
+++ b/docs/audit/theming/scroll-frames.md
@@ -5,21 +5,18 @@
> Vista de conjunto: [README](./README.md) · método y protocolo:
> [`PLAN-theming.md`](../../process/PLAN-theming.md) §1, §2, §7.
-- **Medido**: 2026-08-20 · **Alcance**: **0%** — 0 de 9 knobs por token público
-- **Knobs de apariencia**: 9 — público 0 · privado 1 · global 4 · literal 4 · sistema 0 _(fuera del ratio)_
-- **Contrato hoy** (`lib/recipes/base.ts`): **sin entrada en `base.ts`**
+- **Medido**: 2026-08-21 · **Alcance**: **33%** — 3 de 9 knobs por token público
+- **Knobs de apariencia**: 9 — público 3 · privado 1 · global 1 · literal 4 · sistema 0 _(fuera del ratio)_
+- **Contrato hoy** (`lib/recipes/base.ts`): 4 pública(s) — `loader-inset-block-end`, `loader-font-size`, `loader-letter-spacing`, `loader-fg`
- **Eje `size`**: no · **ficheros**: `scroll-frames.css`
## 1. Knobs fuera de alcance
-### 1.1 Directo a primitivo global (4)
+### 1.1 Directo a primitivo global (1)
| # | fichero:línea | selector | propiedad | valor |
| ---: | --- | --- | --- | --- |
-| 1 | `scroll-frames.css:34` | `[data-scroll-frames-loader]` | `font-size` | `var(--font-size-xs)` |
-| 2 | `scroll-frames.css:36` | `[data-scroll-frames-loader]` | `letter-spacing` | `var(--tracking-widest)` |
-| 3 | `scroll-frames.css:37` | `[data-scroll-frames-loader]` | `color` | `var(--color-neutral-text)` |
-| 4 | `scroll-frames.css:38` | `[data-scroll-frames-loader]` | `opacity` | `var(--opacity-50)` |
+| 1 | `scroll-frames.css:38` | `[data-scroll-frames-loader]` | `opacity` | `var(--opacity-50)` |
### 1.2 A través de un privado (1)
@@ -50,7 +47,7 @@ Consumidos y **no declarados en el CSS** (vienen de `base.ts` o de un estilo inl
## 4. Propuesta de corrección
-### 4.1 Tokens a declarar en `lib/recipes/base.ts` (4)
+### 4.1 Tokens a declarar en `lib/recipes/base.ts` (1)
Valor **verbatim** del CSS de hoy: el default no se mueve, sólo cambia quién
puede moverlo. Nombres derivados de recipe-contract §1 (ejes lógicos, talla
@@ -60,9 +57,6 @@ no distingue lo que debería — se marca `⚠`.
| token (`--scroll-frames-…`) | scope TSC | valor propuesto | usos |
| --- | --- | --- | ---: |
-| `loader-font-size` | `root` | `var(--font-size-xs)` | 1 |
-| `loader-letter-spacing` | `root` | `var(--tracking-widest)` | 1 |
-| `loader-fg` | `root` | `var(--color-neutral-text)` | 1 |
| `loader-opacity` | `root` | `var(--opacity-50)` | 1 |
### 4.2 Sin nombre mecánico (5)
@@ -83,6 +77,18 @@ no distingue lo que debería — se marca `⚠`.
-_(pendiente — lo escribe el autor; se conserva al regenerar)_
+**Medido 2026-08-22.** La receta lo dice de entrada y la medición lo confirma:
+es **layout, no cromo**. Una pista alta cuyo viewport pegajoso fija el medio a
+sangre mientras la pista rueda; lo único que pinta es el rótulo de carga.
+
+1. **Los cuatro knobs del loader entran**: su separación del borde inferior, el
+ tipo, el tracking y la tinta. Ése es el contrato entero.
+2. **`--_scroll-frames-viewport-block-size` se queda privado.** Es la escotilla
+ que el consumidor sobrescribe para encajar el viewport en un contenedor
+ (`100%`) en vez de en la ventana (`100vh`), y su fallback es geometría de
+ LAYOUT, no un valor de tema: quien lo mueve es el sitio de uso, no el tema.
+3. **Fuera del contrato**: los `100%` del medio y de la pista (identidad de
+ layout, D-TH.2), el `line-height: 1` del rótulo (una línea suelta no tiene
+ interlineado) y el `opacity` del sistema.
diff --git a/scripts/theming-sentinel-exceptions.ts b/scripts/theming-sentinel-exceptions.ts
index 3b7f13247..203338647 100644
--- a/scripts/theming-sentinel-exceptions.ts
+++ b/scripts/theming-sentinel-exceptions.ts
@@ -206,6 +206,16 @@ export const SENTINEL_EXCEPTIONS: Record