18 KiB
proof-of-human — alcance de tema: análisis y propuesta
Generado por
node --import tsx/esm scripts/theming-census.ts --report. Lo medido y la propuesta se regeneran; el Veredicto (§5) se conserva. Vista de conjunto: README · método y protocolo:PLAN-theming.md§1, §2, §7.
- Medido: 2026-08-24 · Alcance: 19% — 14 de 75 knobs por token público
- Knobs de apariencia: 79 — público 14 · privado 17 · global 29 · literal 15 · sistema 4 · excepción 16 (los dos últimos, fuera del ratio)
- Contrato hoy (
lib/recipes/base.ts): 20 pública(s) —min-height-sm,min-height-md,min-height-lg,min-height,radius,bg,border,border-width,verified-border,failed-border,locked-opacity,tolerance-opacity,guide-opacity,guide-width,trail-width,goal-width,goal-opacity,token-size,token-radius,token-focus-offset· 2 privada(s) forward —_palette-solid,_palette-element - Eje
size: no · ficheros:clock.css,proof-of-human.css,rotate-align.css
1. Knobs fuera de alcance
1.1 Directo a primitivo global (29)
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | clock.css:29 |
.ck-ring |
fill |
var(--color-surface-raised) |
| 2 | clock.css:30 |
.ck-ring |
stroke |
var(--color-border-default) |
| 3 | clock.css:34 |
.ck-tick |
stroke |
var(--color-border-strong, var(--color-border-default)) |
| 4 | clock.css:39 |
.ck-num |
fill |
var(--color-content-subtle) |
| 5 | clock.css:60 |
.ck-badge-num |
fill |
var(--color-content-on-solid) |
| 6 | clock.css:68 |
.ck-house-frame |
stroke |
var(--color-border-default) |
| 7 | clock.css:87 |
.ck-sill |
fill |
var(--color-content-tertiary, var(--color-content-subtle)) |
| 8 | clock.css:145 |
.ck-hand-main, .ck-hand-tail |
fill |
var(--color-content-primary, var(--color-content-subtle)) |
| 9 | clock.css:148 |
.ck-hub |
fill |
var(--color-content-primary, var(--color-content-subtle)) |
| 10 | clock.css:178 |
[data-proof-of-human][data-state='verified'] .ck-roof |
fill |
var(--color-fulfill-solid) |
| 11 | clock.css:181 |
[data-proof-of-human][data-state='verified'] .ck-badge-dot |
fill |
var(--color-fulfill-solid) |
| 12 | proof-of-human.css:167 |
.poh-trace-dot[data-off-path] |
background |
var(--color-risk-solid) |
| 13 | proof-of-human.css:168 |
.poh-trace-dot[data-off-path] |
box-shadow |
0 0 0 4px color-mix(in srgb, var(--color-risk-solid) 24%, transparent), var(--shadow-2) |
| 14 | proof-of-human.css:180 |
[data-path-trace-token]::before |
min-inline-size |
var(--touch-target) |
| 15 | proof-of-human.css:181 |
[data-path-trace-token]::before |
min-block-size |
var(--touch-target) |
| 16 | rotate-align.css:59 |
[data-rotate-align-needle]:focus-visible |
border-radius |
var(--radius-full) |
| 17 | rotate-align.css:67 |
[data-rotate-align-needle]::before |
min-inline-size |
var(--touch-target) |
| 18 | rotate-align.css:68 |
[data-rotate-align-needle]::before |
min-block-size |
var(--touch-target) |
| 19 | rotate-align.css:83 |
.ra-ring |
fill |
var(--color-surface-raised) |
| 20 | rotate-align.css:84 |
.ra-ring |
stroke |
var(--color-border-default) |
| 21 | rotate-align.css:88 |
.ra-tick |
stroke |
var(--color-border-strong, var(--color-border-default)) |
| 22 | rotate-align.css:93 |
.ra-card |
fill |
var(--color-content-subtle) |
| 23 | rotate-align.css:114 |
.ra-post |
fill |
var(--color-content-tertiary, var(--color-content-subtle)) |
| 24 | rotate-align.css:123 |
.ra-flag |
fill |
var(--color-risk-solid) |
| 25 | rotate-align.css:128 |
[data-proof-of-human][data-state='verified'] .ra-box |
fill |
var(--color-fulfill-solid) |
| 26 | rotate-align.css:131 |
[data-proof-of-human][data-state='verified'] .ra-flag |
fill |
var(--color-fulfill-solid) |
| 27 | rotate-align.css:136 |
.ra-n |
fill |
var(--color-risk-solid) |
| 28 | rotate-align.css:139 |
.ra-s |
fill |
var(--color-content-subtle) |
| 29 | rotate-align.css:142 |
.ra-hub |
fill |
var(--color-content-primary, var(--color-content-subtle)) |
1.2 A través de un privado (17)
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | clock.css:57 |
.ck-badge-dot |
fill |
var(--_proof-of-human-accent) |
| 2 | clock.css:67 |
.ck-house-frame |
fill |
var(--_proof-of-human-accent-soft) |
| 3 | clock.css:72 |
.ck-doorway |
fill |
color-mix(in srgb, var(--_proof-of-human-accent) 22%, black) |
| 4 | clock.css:75 |
.ck-door |
fill |
var(--_proof-of-human-accent-soft) |
| 5 | clock.css:76 |
.ck-door |
stroke |
var(--_proof-of-human-accent) |
| 6 | clock.css:90 |
.ck-roof |
fill |
var(--_proof-of-human-accent) |
| 7 | clock.css:151 |
.ck-hub-dot |
fill |
var(--_proof-of-human-accent-soft) |
| 8 | proof-of-human.css:96 |
.poh-trace-corridor |
stroke |
var(--_proof-of-human-accent-soft) |
| 9 | proof-of-human.css:104 |
.poh-trace-line |
stroke |
var(--_proof-of-human-accent) |
| 10 | proof-of-human.css:114 |
.poh-trace-trail |
stroke |
var(--_proof-of-human-accent) |
| 11 | proof-of-human.css:122 |
.poh-trace-start |
fill |
var(--_proof-of-human-accent) |
| 12 | proof-of-human.css:126 |
.poh-trace-goal |
stroke |
var(--_proof-of-human-accent) |
| 13 | proof-of-human.css:158 |
.poh-trace-dot |
background |
var(--_proof-of-human-accent) |
| 14 | proof-of-human.css:159 |
.poh-trace-dot |
box-shadow |
0 0 0 4px color-mix(in srgb, var(--_proof-of-human-accent) 22%, transparent), var(--shadow-2) |
| 15 | rotate-align.css:98 |
.ra-card.n |
fill |
var(--_proof-of-human-accent) |
| 16 | rotate-align.css:117 |
.ra-box |
fill |
var(--_proof-of-human-accent) |
| 17 | rotate-align.css:120 |
.ra-slot |
fill |
color-mix(in srgb, var(--_proof-of-human-accent) 55%, black) |
1.3 Literales (15)
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | clock.css:21 |
.ck-face, .ck-hand |
inline-size |
100% |
| 2 | clock.css:22 |
.ck-face, .ck-hand |
block-size |
100% |
| 3 | clock.css:203 |
0%, 13% |
opacity |
1 |
| 4 | clock.css:209 |
26%, 62% |
opacity |
1 |
| 5 | clock.css:220 |
to |
opacity |
1 |
| 6 | proof-of-human.css:87 |
.poh-trace-svg |
inline-size |
100% |
| 7 | proof-of-human.css:88 |
.poh-trace-svg |
block-size |
100% |
| 8 | proof-of-human.css:182 |
[data-path-trace-token]::before |
inline-size |
100% |
| 9 | proof-of-human.css:183 |
[data-path-trace-token]::before |
block-size |
100% |
| 10 | rotate-align.css:23 |
[data-rotate-align-skin] |
inline-size |
min(230px, 72%) |
| 11 | rotate-align.css:36 |
[data-rotate-align-dial], [data-rotate-align-needle] |
inline-size |
100% |
| 12 | rotate-align.css:37 |
[data-rotate-align-dial], [data-rotate-align-needle] |
block-size |
100% |
| 13 | rotate-align.css:77 |
.ra-dial, .ra-needle |
inline-size |
100% |
| 14 | rotate-align.css:78 |
.ra-dial, .ra-needle |
block-size |
100% |
| 15 | rotate-align.css:95 |
.ra-card |
font-weight |
600 |
1.4 Excepciones firmadas (16) — fuera del ratio
Literales que llevan su anotación /* literal: <razón> */ en la propia
declaración: la válvula de recipe-contract §3, la misma que honra
component-audit. Una desviación firmada no es deuda — se listan para que la
razón se lea, no para acuñarlas.
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | clock.css:40 |
.ck-num |
font-size |
13px |
| 2 | clock.css:41 |
.ck-num |
font-weight |
600 |
| 3 | clock.css:61 |
.ck-badge-num |
font-size |
11px |
| 4 | clock.css:62 |
.ck-badge-num |
font-weight |
700 |
| 5 | clock.css:103 |
.ck-wing |
fill |
#9ca3af |
| 6 | clock.css:112 |
.ck-bird-body |
fill |
#d1d5db |
| 7 | clock.css:115 |
.ck-bird-head |
fill |
#e5e7eb |
| 8 | clock.css:118 |
.ck-eye |
fill |
#111827 |
| 9 | clock.css:121 |
.ck-glint |
fill |
#ffffff |
| 10 | clock.css:124 |
.ck-letter-paper |
fill |
#fefcf8 |
| 11 | clock.css:125 |
.ck-letter-paper |
stroke |
#cbb892 |
| 12 | clock.css:130 |
.ck-letter-flap |
stroke |
#cbb892 |
| 13 | clock.css:134 |
.ck-stamp |
fill |
#3b82f6 |
| 14 | clock.css:137 |
.ck-beak |
fill |
#fbbf24 |
| 15 | clock.css:138 |
.ck-beak |
stroke |
#d97706 |
| 16 | rotate-align.css:94 |
.ra-card |
font-size |
13px |
2. Sistema transversal (4) — informativo, fuera del ratio
Un tema los alcanza a nivel de sistema, por diseño (recipe-contract §2).
| # | fichero:línea | selector | propiedad | valor |
|---|---|---|---|---|
| 1 | proof-of-human.css:144 |
[data-path-trace-token][data-disabled] |
opacity |
var(--opacity-disabled) |
| 2 | proof-of-human.css:147 |
[data-path-trace-token]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
| 3 | rotate-align.css:54 |
[data-rotate-align-needle][data-disabled] |
opacity |
var(--opacity-disabled) |
| 4 | rotate-align.css:57 |
[data-rotate-align-needle]:focus-visible |
outline |
var(--focus-ring-width) solid var(--focus-ring-color) |
3. Privados de la receta — ¿de dónde sale su valor?
| privado | declaraciones | valor(es) | origen | ¿deriva de un público? |
|---|---|---|---|---|
--_proof-of-human-accent |
2 | var(--color-primary-solid), var(--_proof-of-human-palette-solid) |
global, private | no |
--_proof-of-human-accent-soft |
2 | var(--color-primary-element), var(--_proof-of-human-palette-element) |
global, private | no |
--_proof-of-human-min-h |
1 | var(--proof-of-human-min-height) |
public | sí |
Consumidos y no declarados en el CSS (vienen de base.ts o de un estilo inline del wrapper): --_proof-of-human-palette-element, --_proof-of-human-palette-solid.
4. Propuesta de corrección
4.1 Tokens a declarar en lib/recipes/base.ts (9)
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
al final) y theming §6.7 (slots de color, modificador delante). Un token con
DOS valores distintos es una colisión de nombre: son dos knobs, o el nombre
no distingue lo que debería — se marca ⚠.
token (--proof-of-human-…) |
scope TSC | valor propuesto | usos |
|---|---|---|---|
fill |
root |
⚠ var(--color-surface-raised) / var(--color-content-subtle) / var(--color-primary-solid) / var(--_proof-of-human-palette-solid) / var(--color-content-on-solid) / var(--color-primary-element) / var(--_proof-of-human-palette-element) / var(--color-content-tertiary, var(--color-content-subtle)) / #9ca3af / #d1d5db / #e5e7eb / #111827 / #ffffff / #fefcf8 / #3b82f6 / #fbbf24 / var(--color-content-primary, var(--color-content-subtle)) / var(--color-fulfill-solid) / var(--color-risk-solid) |
45 |
stroke |
root |
⚠ var(--color-border-default) / var(--color-border-strong, var(--color-border-default)) / var(--color-primary-solid) / var(--_proof-of-human-palette-solid) / #cbb892 / #d97706 / var(--color-primary-element) / var(--_proof-of-human-palette-element) |
18 |
font-size |
root |
⚠ 13px / 11px |
3 |
font-weight |
root |
⚠ 600 / 700 |
3 |
bg (ya existe) |
root |
⚠ var(--color-primary-solid) / var(--_proof-of-human-palette-solid) / var(--color-risk-solid) |
3 |
shadow |
root |
⚠ var(--color-primary-solid) / var(--_proof-of-human-palette-solid) / 0 0 0 4px color-mix(in srgb, var(--color-risk-solid) 24%, transparent), var(--shadow-2) |
3 |
width |
root |
⚠ var(--touch-target) / min(230px, 72%) |
3 |
height |
root |
var(--touch-target) |
2 |
radius (ya existe) |
root |
var(--radius-full) |
1 |
4.2 Sin nombre mecánico (13)
- ⚠ decisión:
100%es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar — 10:inline-size,block-size. - ⚠ decisión:
1es un valor identidad o geometría de layout, no un knob de tema — el perímetro de «knob» es D-TH.2, sin firmar — 3:opacity.
4.3 Avisos sobre los tokens propuestos (3)
- el privado
--_proof-of-human-accentdebe pasar a leer este público (o desaparecer) —--proof-of-human-fill,--proof-of-human-stroke,--proof-of-human-bg,--proof-of-human-shadow - el privado
--_proof-of-human-accent-softdebe pasar a leer este público (o desaparecer) —--proof-of-human-fill,--proof-of-human-stroke - ⚠ decisión: el valor es una expresión — el token puede llevar la expresión entera o sólo su término variable —
--proof-of-human-width
4.4 Lo que hay que comprobar a mano (PLAN-theming §1.3 · §7.4)
- Privado que no deriva de un público — §3 lo marca; el privado debe leer el público o desaparecer.
- Velo o acento en el nodo equivocado (
archetype: 'item'en un envoltorio, unbackgrounden shorthand que mata la capa de estado) — se mide desde el píxel hacia arriba. - Doble animación al mover un sello a una superficie con animación propia — registro de
animationstart/animationend. - Diff de computed = 0 en reposo · hover · abierto · disabled · foco, por talla, antes y después.
- Centinela por token nuevo: valor imposible en el root → el nodo lo sigue. Si no, el token miente.
5. Veredicto
Revisión 2026-08-20 — verificación previa a implementación (Opus).
Análisis (§1–§3): CORRECTO en cifras (95 · 0 · 18 · 36 · 37 · 4,
reproducido), con un matiz que cambia la lectura de los literales: de los
37, al menos 20 ya llevan anotación /* literal: … */ (el papel de la
carta #fefcf8, el sello #3b82f6, la paloma #fbbf24, los cielos del
reloj…) — son los colores físicamente fijos que recipe-contract §3 bendice
(«the natural clock's skies»). El censo no ve comentarios, así que los cuenta
como deuda; para R-5 son excepciones por válvula, no tokens.
Propuesta (§4.1): NO IMPLEMENTAR tal cual. La tabla colapsa en nombres
inservibles (fill con 45 usos y 19 valores; stroke 18/8) porque el CSS de
la escena no usa data-attrs sino clases (.ra-card, .ra-needle,
.poh-trace-dot, .ra-tick…) y la derivación no puede leer la parte. Eso
destapa además una deuda propia: hooks de clase en una receta eidos violan
la convención (los ganchos del recipe son data-*; memoria
eidos-lint-no-class-hooks) — el linter no puede clasificarlos.
Disposición (diseño a mano, no mecánico):
- Separar CROMO de ESCENA. Cromo (la tarjeta
.ra-card/root: bg, radius, border, sombra, tipografía del HUD,--_proof-of-human-min-h) → tokens públicos normales; ahí sí valen las filasheight-{sm,md,lg}de §4.1 (expresión enteracalc(var(--size-{k}-control-height) * 3), patrón Sidebar con resueltohost+size:{k}; el root estampadata-size✓). - La escena NO se tokeniza por knob: sus colores son identidad del desafío (anti-bot) y ya están anotados físicamente fijos → excepciones R-5 con razón («escena del captcha, identidad fija»); completar la anotación en los ~17 literales que aún no la lleven.
- El acento SÍ:
--_proof-of-human-accent/-accent-soft(hoyvar(--color-primary-*)con forward de paleta THM-2) es el único eje de color que un tema debe alcanzar — promover a públicoaccent/accent-softy que los privados lo lean. - Deuda aparte (no de este eje): migrar los hooks
.ra-*/.poh-*adata-*del morfo — sin eso,eidos-lintseguirá ciego aquí. Anotarla, no ejecutarla de oficio (no-cascade).
Bloqueos de firma: el perímetro escena-vs-cromo (qué literales son
excepción y cuáles knobs) lo firma el autor con la lista delante · la deuda de
hooks de clase (eje aparte) · D-TH.2 (los 100%/opacity:1).
EJECUTADO 2026-08-20 (parcial, y el resto NO es deuda). Alcance 0 % → 14 %
· contrato 0 → 20 claves públicas. El perímetro escena-vs-cromo tampoco había
que firmarlo: recipe-css-contract ya exime a este componente
(FIXED_TONE_COMPONENTS), así que la escena —paloma, carta, cielos del reloj— es
excepción reconocida y sus literales ya van anotados. Se tokenizó el cromo:
alto por talla (min-height-{sm,md,lg} + resuelto con parts: ['root']), radio,
fondo, borde, bordes de veredicto, la guía del trazo (guide-*, trail-width,
goal-*, tolerance-opacity, locked-opacity) y el token arrastrable.
Dos fallos propios que la verificación cazó, y merece la pena dejarlos escritos:
- Clave duplicada en
recipes/base.ts: añadí un bloque'proof-of-human'sin ver que ya existía uno (el forward de paleta). En un object literal la segunda gana, así que mi bloque se descartaba en silencio — el diff de computed lo delató con 28 diferencias (border-radius: 6px → 0px). Ahora hay un guard trivial que repetiré en cada componente:uniq -dsobre las claves de primer nivel. - Al fusionar los bloques perdí la última clave (
token-focus-offset), y el guard de tokens fantasma lo cazó: la receta consumía un nombre que ya no se declaraba.
Verificación: diff de computed = 0 sobre 406 valores en 7 estados ·
component:audit PASS · suite eidos sin rojos nuevos · rtl:check 0 ·
docs:check 0.
⚠ El centinela no sirve en esta demo: la página declara
min-block-size: 340px y un degradado propio sobre [data-proof-of-human]
(+page.svelte:1042-1048), así que tapa min-height y bg. Los que la demo no
pisa (verified-border, tolerance-opacity) sí responden. Queda dicho en vez de
darlo por verificado.
El 86 % restante son los dos ficheros de la escena (clock.css,
rotate-align.css), que enganchan por CLASE (.ra-*, .poh-*) y pintan
depiction: no son tokenizables sin antes migrar esos hooks a attrs del morfo —
eje aparte, anotado en el README.