Skip to content
Docs
Open Promoeyes
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