uix(color): abre `color` en proof-of-human + exencion fixed-tone formal

Apertura (era el ultimo standalone con cascada):
- El accent del reto (2 slots: solid + element-soft) pasa de 7 bloques
  per-rol al forward compartido en la parte `root` (display:contents →
  cascada a todos los retos, verificado que el skin hereda). Recipe NUEVA
  (su comentario "needs no recipes/base.ts entry" quedaba obsoleto).
- `ProofOfHumanColor = ComponentColorProp` (era ColorRole, README decia
  "8 roles-only"). Root + los 3 retos (clock/path-trace/rotate-align)
  estampan via resolveComponentColor — sus stamps son CSS-muertos hoy, pero
  con el tipo abierto habrian estampado raw strings como data-color.

Exencion fixed-tone (los "2 fallos pre-existentes" del contract):
- FIXED_TONE_COMPONENTS se hoista y comparte entre el test de color Y el de
  font-size, añadiendo proof-of-human: la paloma mensajera + carta son
  paleta identitaria del objeto dibujado (anotada linea a linea) y los
  font-size 13px/11px son unidades de usuario SVG del viewBox, no escala
  tipografica (anotacion añadida al .ra-card que no la tenia). Nota: esto
  tambien exime a natural-time-picker/color-picker del test de font-size
  (hoy limpios; su doctrina fixed-tone ya esta documentada).

card-group: revisado, SIN cambios — card-group-item ya usa `CardColor`
(= ComponentColorProp desde Fase 1): abierto por delegacion.

Verificado en Chrome: default primary = probe exacto (2 slots), risk/jade
exactos, skin hereda por display:contents, custom → seed + mix element.
Contract: mis 2 pre-existentes ELIMINADOS; unico fallo restante =
chat-message `--radius-xs` (WIP vivo de otra sesion, ajeno).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpha-0.1-sec-dom
dev 3 months ago
parent 6bd7c43398
commit 04659558d4

@ -14,6 +14,7 @@
import './rotate-align.css';
import './clock.css';
import { getContext } from 'svelte';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as RotateAlign from '$soma/components/rotate-align';
import type { ClockProps, ProofOfHumanContext } from './types';
@ -21,7 +22,9 @@
const uid = $props.id();
const ctx = getContext<ProofOfHumanContext | undefined>('eidos:proof-of-human');
const resolvedColor = $derived(color ?? ctx?.color ?? 'primary');
// Canonical values stamp `data-color`; raw values stamp the custom marker +
// seed (never a raw string as an attr value).
const colorAttrs = $derived(resolveComponentColor(color ?? ctx?.color ?? 'primary'));
const C = 100;
const R = 88;
@ -39,7 +42,13 @@
}
</script>
<div data-rotate-align-skin data-clock data-color={resolvedColor}>
<div
data-rotate-align-skin
data-clock
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={colorAttrs.customStyle}
>
<RotateAlign.Provider {...rest} targetSnapDeg={30}>
<RotateAlign.Dial>
{#snippet children({ targetAngle, aligned })}

@ -8,13 +8,16 @@
* telemetry all live in soma; this file is the visual channel.
*/
import { getContext } from 'svelte';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as PathTrace from '$soma/components/path-trace';
import type { PathTraceProps, ProofOfHumanContext } from './types';
let { color, size: _size, children: tokenSkin, ...rest }: PathTraceProps = $props();
const ctx = getContext<ProofOfHumanContext | undefined>('eidos:proof-of-human');
const resolvedColor = $derived(color ?? ctx?.color ?? 'primary');
// Canonical values stamp `data-color`; raw values stamp the custom marker +
// seed (never a raw string as an attr value).
const colorAttrs = $derived(resolveComponentColor(color ?? ctx?.color ?? 'primary'));
/** Start / end of the curve, parsed from the `d` string (no layout read). */
function ends(d: string) {
@ -28,7 +31,12 @@
}
</script>
<div data-path-trace-skin data-color={resolvedColor}>
<div
data-path-trace-skin
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={colorAttrs.customStyle}
>
<PathTrace.Provider {...rest}>
<PathTrace.Track>
{#snippet children({ d, corridorPx, progress })}

@ -15,9 +15,10 @@
* flourish (commit-confirm / commit-fail) comes from the generic `commit` motion
* signature (`EidosConfig.motion`), NOT from local @keyframes here (R-4.5).
*
* No public tokens: the recipe consumes global tokens + internal `--_*` vars
* (the accent follows the `data-color` cascade), so it needs no `recipes/base.ts`
* entry — same shape as drag-drop.
* No public tokens: the recipe consumes global tokens + internal `--_*` vars.
* The accent routes through the shared palette forward (`recipes/base.ts`
* `_palette-{solid,element}` on the `root` part), so any role / 33 scale /
* custom value tints it.
*/
[data-proof-of-human-root] {
@ -35,33 +36,13 @@
--_proof-of-human-min-h: calc(var(--size-lg-control-height) * 3);
}
[data-proof-of-human-root][data-color='secondary'] {
--_proof-of-human-accent: var(--color-secondary-solid);
--_proof-of-human-accent-soft: var(--color-secondary-element);
}
[data-proof-of-human-root][data-color='neutral'] {
--_proof-of-human-accent: var(--color-neutral-solid);
--_proof-of-human-accent-soft: var(--color-neutral-element);
}
[data-proof-of-human-root][data-color='affirm'] {
--_proof-of-human-accent: var(--color-affirm-solid);
--_proof-of-human-accent-soft: var(--color-affirm-element);
}
[data-proof-of-human-root][data-color='fulfill'] {
--_proof-of-human-accent: var(--color-fulfill-solid);
--_proof-of-human-accent-soft: var(--color-fulfill-element);
}
[data-proof-of-human-root][data-color='risk'] {
--_proof-of-human-accent: var(--color-risk-solid);
--_proof-of-human-accent-soft: var(--color-risk-element);
}
[data-proof-of-human-root][data-color='threat'] {
--_proof-of-human-accent: var(--color-threat-solid);
--_proof-of-human-accent-soft: var(--color-threat-element);
}
[data-proof-of-human-root][data-color='loss'] {
--_proof-of-human-accent: var(--color-loss-solid);
--_proof-of-human-accent-soft: var(--color-loss-element);
/* Accent colour comes from the shared palette forward
(`--_proof-of-human-palette-{solid,element}`, part `root`): any role /
33 scale / custom value routes through and cascades to every challenge. */
[data-proof-of-human-root][data-color],
[data-proof-of-human-root][data-color-custom] {
--_proof-of-human-accent: var(--_proof-of-human-palette-solid);
--_proof-of-human-accent-soft: var(--_proof-of-human-palette-element);
}
/* ── Stage (orchestrator) ─ */

@ -10,6 +10,7 @@
*/
import { ActiveEidos } from '$uix/eidos';
import { setContext } from 'svelte';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as ProofOfHuman from '$soma/components/proof-of-human';
import type { ProofOfHumanProps, ProofOfHumanContext } from './types';
@ -28,6 +29,10 @@
const eidos = ActiveEidos.require();
const resolvedSize = $derived(eidos.resolve(size, 'md'));
// `color` accepts the full system: canonical → `data-color`, raw value →
// `data-color-custom` + the `--color-custom` seed. The root cascades the
// accent vars to every challenge (it renders `display: contents`).
const colorAttrs = $derived(resolveComponentColor(color));
setContext<ProofOfHumanContext>('eidos:proof-of-human', {
get size() {
@ -39,7 +44,13 @@
});
</script>
<div data-proof-of-human-root="" data-size={resolvedSize} data-color={color}>
<div
data-proof-of-human-root=""
data-size={resolvedSize}
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={colorAttrs.customStyle}
>
<ProofOfHuman.Provider
{...rest}
bind:status

@ -91,7 +91,7 @@
}
.ra-card {
fill: var(--color-content-secondary);
font-size: 13px;
font-size: 13px; /* literal: SVG user units — scales with the dial viewBox, not the type scale */
font-weight: 600;
}
.ra-card.n {

@ -11,13 +11,16 @@
*/
import './rotate-align.css';
import { getContext } from 'svelte';
import { resolveComponentColor } from '$uix/eidos/lib/component-color';
import * as RotateAlign from '$soma/components/rotate-align';
import type { RotateAlignProps, ProofOfHumanContext } from './types';
let { color, size: _size, children: needleSkin, ...rest }: RotateAlignProps = $props();
const ctx = getContext<ProofOfHumanContext | undefined>('eidos:proof-of-human');
const resolvedColor = $derived(color ?? ctx?.color ?? 'primary');
// Canonical values stamp `data-color`; raw values stamp the custom marker +
// seed (never a raw string as an attr value).
const colorAttrs = $derived(resolveComponentColor(color ?? ctx?.color ?? 'primary'));
const C = 100;
const R = 88;
@ -34,7 +37,12 @@
}
</script>
<div data-rotate-align-skin data-color={resolvedColor}>
<div
data-rotate-align-skin
data-color={colorAttrs.dataColor}
data-color-custom={colorAttrs.isCustom ? '' : undefined}
style={colorAttrs.customStyle}
>
<RotateAlign.Provider {...rest}>
<RotateAlign.Dial>
{#snippet children({ targetAngle, aligned })}

@ -1,5 +1,5 @@
import type { Snippet } from 'svelte';
import type { ColorRole, ResponsiveProp, Size } from '$uix/eidos/lib/types';
import type { ComponentColorProp, ResponsiveProp, Size } from '$uix/eidos/lib/types';
import type {
ProviderProps,
ProviderSnippetProps,
@ -11,7 +11,7 @@ import type { PathTraceDifficulty, TokenSnippetProps } from '$soma/components/pa
import type { NeedleSnippetProps } from '$soma/components/rotate-align';
export type ProofOfHumanSize = Extract<Size, 'sm' | 'md' | 'lg'>;
export type ProofOfHumanColor = ColorRole;
export type ProofOfHumanColor = ComponentColorProp;
/** The orchestrator. Purpose- and interaction-agnostic; hosts a challenge. */
export type ProofOfHumanProps = ProviderProps & {

@ -4584,6 +4584,16 @@
--_mark-palette-text: var(--palette-text, var(--color-primary-text));
}
[data-proof-of-human-root] {
--_proof-of-human-palette-solid: var(--color-primary-solid);
--_proof-of-human-palette-element: var(--color-primary-element);
}
[data-proof-of-human-root][data-color], [data-proof-of-human-root][data-color-custom] {
--_proof-of-human-palette-element: var(--palette-element, var(--color-primary-element));
--_proof-of-human-palette-solid: var(--palette-solid, var(--color-primary-solid));
}
[data-skeleton] {
--_skeleton-palette-track: var(--color-primary-track);
}

@ -1789,6 +1789,21 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
'_palette-element': { value: 'var(--color-fulfill-element)', scope: 'host' },
'_palette-text': { value: 'var(--color-primary-text)', scope: 'host' }
},
// ProofOfHuman — `color` tints the challenge accent (path / needle / hand +
// soft corridor) via the shared layer (roles + 33 scales + custom) on the
// `root` part; the root renders `display: contents` and cascades to every
// challenge. Host = primary (the stamped default). The captcha's DEPICTION
// literals (pigeon / letter) stay fixed-tone — see recipe-css-contract.
'proof-of-human': {
'_palette-solid': {
parts: ['root'],
declarations: [{ value: 'var(--color-primary-solid)', scope: 'host' }]
},
'_palette-element': {
parts: ['root'],
declarations: [{ value: 'var(--color-primary-element)', scope: 'host' }]
}
},
// Skeleton — `color` tints the placeholder wash (track slot). Colored-only:
// the `'neutral'` default stamps no `data-color` and keeps the base grey mix,
// so the host value here is the forward fallback only.

@ -38,7 +38,7 @@ const RECIPE_TOKENS = THEME_BASE_RECIPE_TOKENS as unknown as Readonly<
// applies). One shared set so every check in this file agrees; previously only
// the font-size guard excluded them and the other checks red-flagged their WIP.
// Their recipes/CSS re-enter the contract when the tracks land.
const WIP_TRACKS = new Set(['words', 'palabras', 'chronos'])
const WIP_TRACKS = new Set(['palabras', 'chronos'])
/** Extract all CSS values from a recipe entry. Supports the three TSC
* forms: bare string/number, RecipeTokenSingle (one declaration), and
@ -208,16 +208,20 @@ describe('Eidos recipe CSS contract', () => {
expect(violations).toEqual([])
})
// Fixed-tone exception: a component that DEPICTS a physical thing whose
// look must read identically in every theme declares those values as
// annotated literals (never theme-driven). Same doctrine as the Palabras
// rail. `natural-time-picker` paints a daylight band (sky gradient +
// sun/moon knob) — its `--_ntp-sky-*` tokens are theme-independent by
// design, documented in the recipe header. `color-picker` paints the HSL
// wheel itself — its `--color-picker-hue-*` anchors
// (color-picker-spectrum.css) are the wheel, annotated per line.
// `proof-of-human` depicts the messenger pigeon + letter (identity hues)
// and sizes its SVG text in viewBox user units — annotated per line.
// The exemption covers BOTH depiction hues and viewBox-unit font sizes.
const FIXED_TONE_COMPONENTS = new Set(['natural-time-picker', 'color-picker', 'proof-of-human'])
it('keeps raw color values out of component CSS', () => {
// Fixed-tone exception: a component that DEPICTS a physical thing whose
// colour must read identically in every theme declares those hues as
// NAMED recipe tokens with literal values (never theme-driven). Same
// doctrine as the Words rail. `natural-time-picker` paints a daylight
// band (sky gradient + sun/moon knob) — its `--_ntp-sky-*` tokens are
// theme-independent by design, documented in the recipe header.
// `color-picker` paints the HSL wheel itself — its `--color-picker-hue-*`
// anchors (color-picker-spectrum.css) are the wheel, annotated per line.
const FIXED_TONE_COMPONENTS = new Set(['natural-time-picker', 'color-picker'])
const violations = readComponentCssFiles()
.filter(({ component }) => !FIXED_TONE_COMPONENTS.has(component))
.filter(({ css }) => RAW_COLOR_LITERAL.test(css))
@ -228,6 +232,7 @@ describe('Eidos recipe CSS contract', () => {
it('keeps raw font-size literals out of component CSS', () => {
const violations = readComponentCssFiles()
.filter(({ component }) => !FIXED_TONE_COMPONENTS.has(component))
.filter(({ css }) => RAW_FONT_SIZE_LITERAL.test(css))
.map(({ component }) => component)

Loading…
Cancel
Save

Powered by TurnKey Linux.