transparent-ui
transparent-ui
Transparent UI primitives — a floating chat bubble with NOTHING behind it.
Read the rig's notes
THE WHOLE POINT: a ui stage is already transparent. You do not switch it on.
<ui3d> emits its children as REAL MESHES in the host scene. There is no
page, no backing quad, nothing to remove — the card you author is
the only thing that exists (packages/engine/layers/ui/_stage.ts,
createScene3D).
<ui> renders to a render target that is CLEARED TO ALPHA 0, and mounts
on a plane whose material is transparent + depthWrite:false
(_stage.ts renderRT / packages/engine/lib/ui-stage-plane.ts). Authoring
`fill=` / `background=` on the stage is the ONLY thing that paints
an opaque page — omit it and the pane is glass.
So the three ways people accidentally get a black rectangle are:
1. `fill=` on the STAGE tag → scene 02 shows both halves
2. a wrapper `card()` left on its default fill (#15171f, card.ts)
→ every wrapper here is `fill=transparent shadow=false`
3. a bare `<ui>` / `<ui3d>` with no width/height — it defaults to the
FULLSCREEN 1920x1080 wall (xml-scene.ts UI3D_DEFAULTS), so a bubble-
sized stage must always author its own dims.
THE BUBBLE RIG — a bubble is ONE card. A card takes a `path`: a plain SVG
`d` string in the card's own coordinates (origin its top-left, y down, the
same stage px `w`/`h` are measured in), which REPLACES the rounded rect.
card(w=430 h=92 fill=#3b3b3d path="M215 0 L384 0 A46 46 0 0 1 430 46 …")
`w`/`h` stay the LAYOUT box — flow measures them, children lay out inside
them — while the path is only what gets painted, so it may leave that box
freely. A chat-bubble tail is nothing more than a path that dips below `h`.
`r` no longer applies: the path already says where the corners are. The card
grows its own surface padding to whatever the path reaches.
Layering a shape behind a rounded rect — a rotated card, a tinted tail icon —
cannot be made to work, and the failure is not a positioning problem:
· the two fills DOUBLE-BLEND wherever they overlap, so any translucency
shows the join as a darker wedge;
· a `stroke` stops dead at the card's edge and the tail reads as a
detached blob beside an outlined bubble;
· a `gradient` restarts inside the tail instead of running through it.
One outline fixes all three at once — fill, gradient, stroke and shadow
follow the whole silhouette because there is only one shape to paint. Scene
07's blue tails are the proof: nothing in this reel matches that gradient by
hand. See readCardPath in packages/engine/layers/ui/_draw.ts.
WORLD DIALECT — mm, camera at z≈760 (packages/core/units.ts). `world_width` maps
the pixel stage onto that world box; `world_height` derives from the stage
aspect, so authoring only `world_width` never trips the aspect warning.
01. no page — three ui3d bubbles over three spheres: the world reads
through every gap, and nothing was switched off to get it
02. fill — the same 2D <ui> twice, one with `fill=` and one without;
the left half is the bug, the right half is the fix
03. rig — the flying conversation: cascade in, yaw for depth, float
to idle, tails and a blinking typing bubble
04. glass — flat translucency (rgba fill) beside real refraction
(material=physical + transmission) over the same spheres
HOUSE STYLES (scenes 05–07) — the same one-card bubble dressed as the chat
apps people actually picture. Style references, not brand assets: no logos,
no wordmarks, no app chrome — just the geometry each one is known for, which
is a `path`, a `fill` and nothing else.
05. messenger — full pills (r = h/2), NO tail at all, so this is the one
family that needs no path — grey in / gradient out, avatar
on the last incoming, and a reaction pill hung off a
bubble's bottom edge with a negative margin
06. whatsapp — tight 14px corners, dark green out / slate in, the notch
pathed at the TOP corner (and absent on a grouped
follow-up), timestamp + double-tick inside each bubble
07. imessage — full pills again, tails bottom-left / bottom-right, the
outgoing ones on a vertical gradient that runs straight
through the tail
Scene 05 lays its thread out with FLOW — a column of transparent rows, each
row only choosing `justify=start` or `justify=end`, every bubble `w=hug`.
Scenes 06/07 use ABSOLUTE x/y with explicit widths, partly so the thread
staggers where I wanted it and partly because a `path` is authored against a
known `w`/`h`, which a `w=hug` bubble does not have until layout runs.
Matched haze (hz-a/hz-b/hz-c) rides every scene so transition=match-move
carries the backdrop across the cuts.
Preview: /packages/runtime/index.html?project=promos/primitives/transparent-ui/transparent-ui.promo- Topic
- uis
- Scenes
- 7
- Kind
- reference rig
//
Transparent UI primitives — a floating chat bubble with NOTHING behind it.
THE WHOLE POINT: a ui stage is already transparent. You do not switch it on.
<ui3d> emits its children as REAL MESHES in the host scene. There is no
page, no backing quad, nothing to remove — the card you author is
the only thing that exists (packages/engine/layers/ui/_stage.ts,
createScene3D).
<ui> renders to a render target that is CLEARED TO ALPHA 0, and mounts
on a plane whose material is transparent + depthWrite:false
(_stage.ts renderRT / packages/engine/lib/ui-stage-plane.ts). Authoring
`fill=` / `background=` on the stage is the ONLY thing that paints
an opaque page — omit it and the pane is glass.
So the three ways people accidentally get a black rectangle are:
1. `fill=` on the STAGE tag → scene 02 shows both halves
2. a wrapper `card()` left on its default fill (#15171f, card.ts)
→ every wrapper here is `fill=transparent shadow=false`
3. a bare `<ui>` / `<ui3d>` with no width/height — it defaults to the
FULLSCREEN 1920x1080 wall (xml-scene.ts UI3D_DEFAULTS), so a bubble-
sized stage must always author its own dims.
THE BUBBLE RIG — a bubble is ONE card. A card takes a `path`: a plain SVG
`d` string in the card's own coordinates (origin its top-left, y down, the
same stage px `w`/`h` are measured in), which REPLACES the rounded rect.
card(w=430 h=92 fill=#3b3b3d path="M215 0 L384 0 A46 46 0 0 1 430 46 …")
`w`/`h` stay the LAYOUT box — flow measures them, children lay out inside
them — while the path is only what gets painted, so it may leave that box
freely. A chat-bubble tail is nothing more than a path that dips below `h`.
`r` no longer applies: the path already says where the corners are. The card
grows its own surface padding to whatever the path reaches.
Layering a shape behind a rounded rect — a rotated card, a tinted tail icon —
cannot be made to work, and the failure is not a positioning problem:
· the two fills DOUBLE-BLEND wherever they overlap, so any translucency
shows the join as a darker wedge;
· a `stroke` stops dead at the card's edge and the tail reads as a
detached blob beside an outlined bubble;
· a `gradient` restarts inside the tail instead of running through it.
One outline fixes all three at once — fill, gradient, stroke and shadow
follow the whole silhouette because there is only one shape to paint. Scene
07's blue tails are the proof: nothing in this reel matches that gradient by
hand. See readCardPath in packages/engine/layers/ui/_draw.ts.
WORLD DIALECT — mm, camera at z≈760 (packages/core/units.ts). `world_width` maps
the pixel stage onto that world box; `world_height` derives from the stage
aspect, so authoring only `world_width` never trips the aspect warning.
01. no page — three ui3d bubbles over three spheres: the world reads
through every gap, and nothing was switched off to get it
02. fill — the same 2D <ui> twice, one with `fill=` and one without;
the left half is the bug, the right half is the fix
03. rig — the flying conversation: cascade in, yaw for depth, float
to idle, tails and a blinking typing bubble
04. glass — flat translucency (rgba fill) beside real refraction
(material=physical + transmission) over the same spheres
HOUSE STYLES (scenes 05–07) — the same one-card bubble dressed as the chat
apps people actually picture. Style references, not brand assets: no logos,
no wordmarks, no app chrome — just the geometry each one is known for, which
is a `path`, a `fill` and nothing else.
05. messenger — full pills (r = h/2), NO tail at all, so this is the