Skip to content

feat(aesthetic): look-and-feel uplift on the live render path - #116

Merged
komod0 merged 3 commits into
mainfrom
feat/look-and-feel-uplift
Jun 7, 2026
Merged

feat(aesthetic): look-and-feel uplift on the live render path#116
komod0 merged 3 commits into
mainfrom
feat/look-and-feel-uplift

Conversation

@komod0

@komod0 komod0 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

What & why

Generated UI currently collapses toward sameness the moment it renders — one shared heading treatment, one flat card border + shadow, one identical fade-up entrance — even though each aesthetic defines its own font, glow strength, radius, and motion personality. This PR wires that already-defined per-preset identity into the live SurfaceRenderer so a noir dossier, a cyber hologram, a nostromo terminal, a gothic manuscript, and a minimal sheet read as genuinely different objects on screen.

This is the "look-and-feel uplift" flagship from a multi-agent visual audit (56 ideas → ground-checked against real code). Everything here touches the live render path and reaches all 5 presets.

Changes

  • Per-preset entranceChildList now derives childVariants.hidden from entranceHiddenVariant(motionPersonality.entrance): cinematic (noir, drifts up+in), glitch (cyber, snaps from a shrunk scale), terminal (nostromo, prints down), candle (gothic, swells from small scale), crisp (minimal, short hop). The reduced-motion branch (plain children, no motion) is unchanged.
  • Editorial heading voice — generated h1–h5 get an a2ui-heading hook; globals.css applies per-preset letter-spacing + weight. The loaded font is deliberately not swapped — noir's typewriter headers are its identity (see note below).
  • Material-aware cards — each data-effect-card material gains a directional gradient over its fill, a material-aware top border, and (paper/parchment) an accent glow halo scaled by --aesthetic-glow-strength. Minimal gets a faint anchor (accent hairline + layered shadow), staying clean by design.
  • Text glow — noir (amber) + gothic (blood-candle) headings/accent text, scaled by glow-strength; minimal none. Light paper/parchment cards reset text-shadow on all descendants so dark ink stays crisp.
  • Theme radius on controls — Button / TextField / Table corner radius reads --aesthetic-radius (badge stays rounded-full; card base class unchanged so the existing border-t-2 test holds).

A note on the "wire the heading font" idea

The audit's #1 suggestion was to swap headings to each preset's fonts.heading. On inspection that's a noir regression: fonts resolve via CSS vars (font-typewriter--aesthetic-font-body), 4/5 presets already share body+heading family, and the only one that would change is noir — swapping its signature typewriter headers for sans. So I delivered the "editorial swing" via spacing/weight treatment instead.

Testing

  • +5 renderer tests: heading hook present, noir font NOT swapped, Button + TextField radius var, card material attribute. Verified the radius test fails without the change.
  • Full gate green: tsc 0, eslint 0, stylelint 0, prettier clean, 1198 vitest pass, next build OK.
  • Adversarially reviewed via a 4-angle workflow (CSS specificity, contrast/legibility, cross-preset leakage, motion correctness). 3 angles found nothing; the one low-severity finding (incomplete text-shadow reset scope) is fixed in this PR.

Decisions baked in (per request)

Minimal → faint anchor (not bare-flat) · type voice → full editorial swing · button tap sound → out of scope (opt-in, separate track).

Branched off main, independent of the open PRs #114/#115.

komod0 added 3 commits June 7, 2026 15:57
Make generated UI read as genuinely distinct per aesthetic the moment it
appears — not "noir with different colors" — by wiring per-preset identity
that was already defined but never reached the live SurfaceRenderer.

- ChildList: per-preset entrance physics (cinematic/glitch/terminal/candle/
  crisp) via entranceHiddenVariant, instead of one shared fade-up. The
  reduced-motion path (plain, un-animated children) is untouched.
- TextRenderer: tag generated h1–h5 with `a2ui-heading`, the hook for a
  per-preset editorial treatment (letter-spacing + weight) in globals.css.
  The loaded face is deliberately NOT swapped — noir's typewriter headers are
  its identity; only spacing/weight diverge.
- Cards: material-aware top border + a subtle directional gradient over each
  material's fill, plus an accent glow halo scaled by --aesthetic-glow-strength
  (paper/parchment). Minimal gets a faint anchor (accent hairline + layered
  shadow), staying clean by design.
- Text glow: noir (amber) + gothic (blood-candle) headings/accent text, scaled
  by glow-strength; minimal none. Light paper/parchment cards reset text-shadow
  on all descendants so dark ink stays crisp.
- Controls: Button/TextField/Table corner radius now reads
  --aesthetic-radius (badge stays rounded-full; card base unchanged).

+5 renderer tests (heading hook, noir font NOT swapped, control radius var,
card material attr). Full gate green: tsc/eslint/stylelint/prettier, 1198
vitest, next build. Adversarially reviewed (4 angles); the one low-severity
finding — incomplete text-shadow reset scope — is fixed here.
The +5 tests were added after the prettier --write pass, so two
makeSurface calls weren't wrapped to the print width. CI's
prettier --check flagged them. Formatting-only; no logic change.
…dren)

A "generate 3 takes" run could leave take 3 partially blank: the top-level
hr/figure-row/warning children stayed frozen at opacity:0 with the gothic
candle hidden transform, while children 1–2 and all nested content rendered
fine.

Cause: ChildList revealed children via a parent `staggerChildren` orchestrator
(a `display: contents` motion.div propagating the show label down the variant
cascade). Take 3 is shown the instant generation finishes — while its <img>
is still loading. When the image resolves, the subtree re-renders mid-cascade,
the orchestration resets, and the siblings that hadn't been cued yet are
stranded at their hidden opacity:0 forever. Takes 1/2 escape only because
they're viewed later when nothing is re-rendering.

Fix: drop the parent conductor; each child animates itself with its own
initial→animate + a per-index delay reproducing the stagger. `animate` is a
fixed target framer-motion settles on and never reverts from across
re-renders/remounts, so no mid-flight re-render can leave a sibling behind.
The per-preset entrance physics and reduced-motion path are unchanged; children
remain the parent flex/grid's direct items (as with the old display:contents).

+2 regression tests: no display:contents orchestrator is rendered, and every
child gets its own animated wrapper. Verified the first fails if the
orchestrator is reintroduced. Gate green: tsc/eslint/prettier, 1200 vitest,
build.
@komod0
komod0 merged commit 9c923d5 into main Jun 7, 2026
2 checks passed
@komod0
komod0 deleted the feat/look-and-feel-uplift branch June 7, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant