<!-- 04_VISUAL/xwui/xwui-lib/xwui-lib-super/src/layout/XWUILayerStage/XWUILayerStage.md -->
# XWUILayerStage

Several parallel planes drawn by xw3d (plnx REF_06 C14, REF_11 WP3-C), each carrying live XWUI content or positioned
nodes: explode and assemble with an eased spacing, select, dim and lit states, cross-layer edges with a trace, pulses along
the lit edges, a sequence of layers or a polyline on one plane, beams with tags standing on a plane, and four arrangements:
a stack, a row, one plane, and the faces of a cube (the dimension cube). A layer chooser (a real radiogroup) sits over the
scene and a nested-list twin is the flat face. It composes XWUISceneStage (C12) for the camera, the stage, the toolbar and
the 2D face.

**How it is drawn.** The planes are glass slabs, every node is one instance of ONE instanced sphere part (trace dimming is
an instance colour), every edge is a polyline, beams are standing walls, and the floor, the pulses (a stem and a spark, a
path and a dot, one dot per lit edge) and the plane names are parts too: `layerStageParts(input)` builds the whole list from
the logic's state (`logic.parts()`), and the canvas diffs it by id. It needs WebGL2. Without it, or on a low-power device
until the reader taps the 3D switch, the nested-list twin is the face (never CSS 3D). The stage writes no CSS 3D of its
own; xw3d writes every perspective and transform.

Over the drawing sit ordinary 2D elements: the node **chips** (label, value, state; pinned on the node, at most 6 on a
phone, 4 low power, 40 on a desktop, `chipMax` overrides; the other nodes stay markers), the chooser, the caption and the
toolbar. **Live content is flat**: the selected layer's content sits in the focus card (with the detail card of `hudSlot`),
every other layer's in a small upright card pinned on its plane's projected centre.

## Basic Usage

```example
file: examples/BasicUsage.ts
html: examples/BasicUsage.html
title: Basic Usage
description: The plan model as six glass layers with a chooser, a layer pulse and live content.
```

```example
file: examples/TraceGraph.ts
html: examples/TraceGraph.html
title: Trace Graph
description: Objectives traced across planes to initiatives, budget lines and KPIs.
```

```example
file: examples/DimensionCube.ts
html: examples/DimensionCube.html
title: Dimension Cube
description: Three planes on the faces of a cube.
```

## Data

| Field | Shape | Meaning |
| --- | --- | --- |
| `layers` | `{ id, label, sub?, hue?, icon?, nodes?, content? }[]` | Planes, top to bottom. Ids are unique across layers and nodes (first wins). `hue` is a hue token NAME (`--xwui-hue-<name>`, slate when unknown). `content` is a pure XWUI projection; live children use `mountContent` or `contentSlot`. |
| `layers[].nodes` | `{ id, label, x, y, kind?, value?, hue? }[]` | Nodes on the plane at x, y percent of its width and height (authored left to right). `kind`: `chip` (default; folds to a dot when a trace leaves it out), `primary` (keeps its label), `dot` (label only when lit or selected). |
| `edges` | `[from, to][]` | Directed edges between node (or layer) ids; drawn as curves in the same projection. Unknown, self and repeated edges are dropped. |
| `beams` | `{ layerId, at, label, tone }[]` | A wall standing on a plane at `at` percent of its width with a tag (data date, finish line). `tone` is a hue NAME. |
| `pulse` | `{ path, layerId?, captions? }` | `path` of layer ids: a pulse runs once, one step per layer (`pulses: 'layers'`). `path` of `[x, y]` percent points: a dot loops along it on `layerId` (`pulses: 'path'`). |
| `selectedId` | string or null | The selected layer or node. A node lights its trace. |
| `litId` | string or null | A layer the host lights; the others dim. A running layer pulse lights its own. |
| `camera`, `view` | as C12 | Camera the host asks for (authored left to right); the face (`'3d'`, `'2d'`, unset = policy). |
| `summary`, `caption` | string | The sentence that says what the drawing shows (default: the layer and node counts); a live sentence over the scene (the trace sentence a host builds on `traceChange`). |
| `status`, `statusText` | as C12 | Loading, empty, error. No layers is the empty state (`emptyText`). |
| `contentSlot`, `sideSlot`, `hudSlot` | `(host, ctx) => cleanup?` | Page fills (runtime only): a layer's live content (`{ layer }`, drawn flat: the focus card while selected, a small pinned card otherwise), beside a chooser entry (`{ layer }`, shown while checked), the detail card (`{ selectedId }`, filled again when the selection moves). Each host survives re-renders, the 2D switch and arrangement changes; selecting a layer MOVES its host between the two places, it never rebuilds it. |

## Options

| Option | Default | What it does |
| --- | --- | --- |
| `arrangement` | `'stacked'` | `'stacked'`, `'side'` (a row; reading order flips on a right-to-left page), `'single'` (the selected plane only, ForOps tilt), `'box'` (the first three layers on the top, front and visible side faces of a cube). |
| `exploded` | `true` | Exploded or assembled; a change eases the spacing over 900 ms (reduced motion snaps). |
| `gap` | `{ exploded: 90, assembled: 15 }` | Spacing in px (number or `'<n>px'`). In `box` half the spacing opens the seams. |
| `plane`, `boxSize` | `380 x 226`, `260` | Plane size and cube edge in px before the camera. |
| `backdrop` | `'glass'` | The slab's opacity: `'glass'` translucent, `'solid'` opaque (a lit plane never shows the plane behind it through it; dimmed ones stay mostly there), `'transparent'`. |
| `tintStrength` | `'normal'` | How much of its hue a plane shows: `'soft'` (glass at about 60 percent of today's opacity), `'normal'` (the look the stage always had, markup and parts unchanged), `'deep'` (about 1.7 times the opacity AND the slab leaves the scene lights: the lights add up to about 1.9 on a face that looks at the viewer, which is what washes a hue toward white). The hue fill of the chips, the chooser rows, the badge and the twin rows scales with it (x0.6, x1, x1.4: the widest fill, a lit chip at 30 percent, stays at 42 percent at `'deep'`, so the scene ink keeps 4.5:1 on every hue). Solid slabs are opaque already: only the light blend changes. A bad value is `'normal'`. Example: `{ tintStrength: 'deep' }`. |
| `trace` | `true` | Selecting a node lights its upstream and downstream chain and dims the rest. |
| `pulses` | `'edges'` | `'edges'` (dots on the lit edges, looping), `'layers'` (a step per layer, once), `'path'` (a dot looping on a polyline), `'none'`. |
| `pulseStepMs`, `pulseLoopMs` | `1280`, `3600` | One layer step; one loop of the dots. |
| `tour` | none | `{ intervalMs, ids? }`: cycles the selection through `ids` (default the chooser's). Off by default under reduced motion and on touch; a reader's pick restarts the interval; a toolbar toggle pauses it. |
| `view` | policy | Face to open on. |
| `camera` | per arrangement | C12 options over ours: floor plane, rest yaw -36 pitch 60 (stack), the prototype's tilt range, perspective 1700, `scaleToFit` from the projected bounds of the exploded planes. `camera: { fit: 'fill' }` takes the host's whole box and keeps that design ratio by scale. See XWUISceneStage.md, "Sizing: design and fill". A host `rest` without a `range` takes C12's range around it. |
| `chooser`, `choices`, `chooserLabel` | `'hud'`, every layer | The radiogroup over the scene and the ids it lists (layers or nodes). |
| `floor` | `true` | A grid floor under a stack or a cube. |
| `chipMax` | device | Cap on node chips: 6 on a phone, 4 low power, 40 desktop (halved at camera quality `'low'`). Each chip restyles on every camera change (measured 0.6 to 0.9 ms each at the phone class). |
| `ariaLabel`, `emptyText` | locale pack | Accessible name of the scene; text of the empty state. |
| `locale`, `numberingSystem` | page, `'latn'` | Plane numbers (`01`) in Western digits unless asked. |

## Events and methods

Events (`on<Event>(handler)` or `logic.on(name)`): `select { id, layerId }`, `explodeChange { exploded }`, `traceChange { ids }`,
`pulseStep { id, index }`, `pulseEnd`, `tourChange { running }`; the camera's through `onCameraChange`, `onViewChange`.

Methods: `select(id)`, `getSelectedId()`, `getTrace()`, `setExploded(on)`, `isExploded()`, `toggleExploded()`, `playPulse()`,
`stopPulse()`, `setTour(on)`, `setCamera(camera, { animate? })`, `getCamera()`, `command(name)`, `setView(view)`,
`toggleView()`, `getView()`, `mountContent(layerId, mount, slot?)`, `getSlot(part, layerId?)`, `getCanvas()` (the
XWUISceneCanvas: `pick`, `project`, `capture`), `onUnsupported(handler)` (webgl cannot run; the twin is the face), `updateData()`,
`updateConfig()`, `getElement()`, `getViewport()`, `destroy()`. Headless: `logic.sceneInput()` and `logic.parts()`.

Live content: `mountContent(layerId, (host, createViewMount) => child, slot = 'layer' | 'side')` runs once per host with the
engine factory the stage renders with; return the child (`{ destroy() }`) or a cleanup. The stage destroys it when the layer
leaves, the registration is replaced or released (the returned function), or the stage is destroyed. Selection, explode,
arrangement and the 2D switch keep it. Ignored for an unknown id and for a plane carrying pure `content` (that is drawn by the
view in the focus card, for the selected layer only).

Where the content shows: on a wide 3D face the focus card is at the end side over the drawing and the other layers' cards sit
on their planes; on a narrow stage (below 52rem) and on the flat twin the focus card is below the stage in flow and the
pinned cards are not drawn (their hosts stay mounted). Clicking a pinned card's title selects its layer.

CSS hooks: `--xwui-layer-stage-chip` (chip scale, on the root), `-hue` (chip and card), `-tint-k` (the factor on a hue fill, written by the classes `xwui-layer-stage-tint-soft` and `-tint-deep`; a stage without the class reads 1); C12's `--xwui-scene-stage-*` and
`--xwui-scene-stage-block-size`. The old per-frame properties (`-gap`, `-lift`, `-k`, `-xk`, `-up`, `-nx`, `-ny`, `-nz`, `-shown`) are gone.

## Accessibility and RTL

- The drawing is an image: C12's focusable viewport `group` (`aria-roledescription` "3D scene") carries the name and the
  written summary; the surface (the canvas, the node chips and the plane names) is `aria-hidden`. The chooser is a real `radiogroup` bound to the
  same selection: arrows move and select and wrap (horizontal arrows follow the reading direction), Home and End jump, one
  tab stop. The flat twin (default under reduced motion and below 560 px) is a nested list of layers and their nodes as
  pressed buttons, with "In trace" and "Lit" in words and the lit edges as sentences, so colour is never the only cue.
- Every pointer action has a keyboard path: planes and nodes through the chooser or the twin; explode, pulse, tour and 2D
  through toolbar toggles (`aria-pressed`); camera keys on the viewport (C12); Escape stops a drag, rotate mode or a pulse.
- Reduced motion, in JS and CSS: no spacing tween, no travelling dots, no dash animation, no tour; layer pulse steps still
  advance (user-started, with Stop). Touch: C12's `pan-y` (no scroll trap).
- Tint strength: the flat parts keep their ink at 4.5:1 at every strength (the contrast test measures the chip, the chooser row and the twin row on all 17 hues in the night scope); the hue is never the only cue (the chip ring, the pressed state and the words are unchanged). The slab opacity is data for the engine, so reduced motion and forced colours are untouched.
- RTL: direction from the parent `dir`; the camera mirrors (yaw), the world box stays left to right, a row of planes flips to
  reading order, the chooser sits on the inline-start side. User text is `dir="auto"`; digits Western unless asked.

## Provenance

PROMOTED from the app-owned `03_BASE/xwflow/xwflow-web/src/components/XWUIPlaneStack` (703 lines: stacked, side, single,
`mountContent`, `selectedId`, keyboard twin) and extended per REF_06 2.3: explode, nodes and edges, trace, pulses, beams,
`box`, camera through C12. Its regression floor (arbitrary counts, malformed and duplicate ids, finite geometry, keyboard
direction, slots kept across arrangement changes) is ported into `XWUILayerStage.test.ts`. Kept from it: layer identity
independent of position, array order top to bottom, the `mountContent(id, factory, slot)` seam with the engine factory, pure
`content` projections, the text twin. Changed: tones are hue token names (any declared hue, not ten fixed names); the side
column is the chooser HUD plus the 2D twin; `single` is one fitted plane rather than natural-height content; events are logic
events (`on<Event>`), not bubbling DOM events. `xwflow-web` keeps its copy until its owner switches imports (no shim).
Prototype sources: Web-Platform (six layers, gap 90/15, lift 16, pulse steps 16 x 80 ms), Web-ForCorporate (27 nodes, 34
edges, trace, tour 60 x 90 ms), Web-ForPMs (beams, path pulse), Web-ForOps (single plane tilt 18..34), Web-Spatial (explode
toggle), PX-Model (cube).

```api
```
