diff --git a/src/uix/eidos/active-eidos-config.test.ts b/src/uix/eidos/active-eidos-config.test.ts index 6e402c06a..f7fed711d 100644 --- a/src/uix/eidos/active-eidos-config.test.ts +++ b/src/uix/eidos/active-eidos-config.test.ts @@ -552,6 +552,15 @@ describe('ActiveEidos config', () => { ); }); + it('emits the Fase 3 eventful corner morph (@property + press-squeeze --shape-smoothing)', () => { + const css = createThemeBaseEidos().renderStaticCss(); + // registered as a typed number so the corner morph interpolates instead of jumping + expect(css).toContain('@property --shape-smoothing {'); + expect(css).toContain("syntax: '';"); + // the cross-modal contact firma (press-squeeze) firms the corner at the press peak + expect(css).toContain('--shape-smoothing: 3;'); + }); + it('emits the Phase 3 typography scales + optical tracking', () => { const css = createThemeBaseEidos().renderStaticCss() expect(css).toContain('--tracking-tight: -0.02em;') diff --git a/src/uix/eidos/generated/base.css b/src/uix/eidos/generated/base.css index 9b2b5b9be..97575736f 100644 --- a/src/uix/eidos/generated/base.css +++ b/src/uix/eidos/generated/base.css @@ -4363,6 +4363,12 @@ z-index: var(--depth-recessed-z); } +@property --shape-smoothing { + syntax: ''; + inherits: true; + initial-value: 2; +} + [data-shape='rounded'] { corner-shape: round; } @@ -4758,6 +4764,7 @@ transform: scale(0.96); filter: brightness(0.96); box-shadow: 0 0 0 0 transparent; + --shape-smoothing: 3; } 100% { transform: scale(1); diff --git a/src/uix/eidos/lib/motion/presets/css.ts b/src/uix/eidos/lib/motion/presets/css.ts index c851de986..4bd9c6546 100644 --- a/src/uix/eidos/lib/motion/presets/css.ts +++ b/src/uix/eidos/lib/motion/presets/css.ts @@ -149,10 +149,18 @@ export const BUILTIN_KEYFRAMES: Readonly> = { 'present-rise': { from: { opacity: '0', transform: 'translateY(8px)', 'box-shadow': '0 0 0 0 transparent' } }, - // Contact: the shadow flattens to the surface at the press peak, then returns (recede). + // Contact: cross-modal press firma — scale (motion) + shadow flattens to the surface (depth) + + // the corner firms (shape: `--shape-smoothing` rises → squircle squares up). Only set at the + // peak so it interpolates from/to the element's underlying smoothing (respects a theme + // override); a no-op for non-`continuous` corners, which don't read `--shape-smoothing`. 'press-squeeze': { '0%': { transform: 'scale(1)' }, - '40%': { transform: 'scale(0.96)', filter: 'brightness(0.96)', 'box-shadow': '0 0 0 0 transparent' }, + '40%': { + transform: 'scale(0.96)', + filter: 'brightness(0.96)', + 'box-shadow': '0 0 0 0 transparent', + '--shape-smoothing': '3' + }, '100%': { transform: 'scale(1)' } }, 'commit-settle': { diff --git a/src/uix/eidos/lib/render-css.ts b/src/uix/eidos/lib/render-css.ts index 507d97bab..70f5c70b4 100644 --- a/src/uix/eidos/lib/render-css.ts +++ b/src/uix/eidos/lib/render-css.ts @@ -839,6 +839,12 @@ function appendShapeDeclarations(declarations: string[], shape: ShapePrimitiveSe */ function renderShapeBlocks(shape: ShapePrimitiveSet): string[] { const blocks: string[] = [] + // Register --shape-smoothing as a typed number so the eventful corner morph (the press-squeeze + // firma, SHAPE_ENGINE_RFC Fase 3) interpolates smoothly instead of jumping. inherits:true keeps + // the :root default + any theme override cascading to every element. + blocks.push( + `@property --shape-smoothing {\n\tsyntax: '';\n\tinherits: true;\n\tinitial-value: ${shape.smoothing};\n}` + ) for (const [family, value] of Object.entries(shape.families)) { blocks.push(renderBlock(`[data-shape='${family}']`, [`corner-shape: ${value};`])) }