# HTML API

`glyphcss/elements` is a side-effect import that registers a set of
`<glyph-*>` custom elements with `customElements`. After the import runs,
you can use them in plain HTML — no React, no Vue, no build step required.

## Install

```bash
pnpm add glyphcss
```

```html
<script type="module" src="https://esm.sh/glyphcss/elements"></script>
```

Or in a bundled app:

```ts
import "glyphcss/elements"; // side-effect — registers customElements
```

Re-imports are idempotent (the module checks `customElements.get` before
defining). In non-DOM environments (SSR, Node) the module silently no-ops.

## Element tree

Camera wraps scene. Attribute names are kebab-case (`rot-x`, `rot-y`,
`hidden-lines`, `auto-size`). Boolean attributes follow standard HTML
convention — present means true.

```html
<glyph-perspective-camera rot-x="65" rot-y="45" zoom="50" distance="3">
  <glyph-scene mode="solid" cols="100" rows="30">
    <glyph-orbit-controls drag wheel></glyph-orbit-controls>
    <glyph-mesh geometry="cube"></glyph-mesh>
    <glyph-hotspot hotspot-id="top" at="0,0.5,0" size="3,2"></glyph-hotspot>
  </glyph-scene>
</glyph-perspective-camera>
```

## Elements

### `<glyph-scene>`

Root container. Owns the `<pre>` rasterisation output and the lighting state.

| Attribute | Type | Notes |
|---|---|---|
| `mode` | `"wireframe" \| "solid" \| "voxel" \| "ink"` | Default `solid` |
| `cols` | number | Grid width in character columns |
| `rows` | number | Grid height in character rows |
| `cell-aspect` | number | Height ÷ width of the cell (default `2.0`) |
| `glyph-palette` | string | Named palette: `default`, `detail`, `ascii`, `lines`, `blocks`, `dots`, `solid`, `braille`, `runes`, `stars`, `arrows`, `math`, `binary`, `hex` |
| `char-mode` | `"ascii" \| "braille" \| "halfblock" \| "quadrant"` | Character encoding for rasterized output (default `"ascii"`). `"braille"` (wireframe-only) packs a 2×4 subcell dot grid into Unicode Braille Patterns for smoother edges. `"halfblock"` (solid-only) packs two independently colored subcells (top/bottom) into `▀`/`▄`/`█` for 2× vertical color resolution. `"quadrant"` (solid-only) generalizes that to a full 2×2 subcell split (16 possible glyphs) for both shape AND color resolution. Each is a documented no-op outside the mode it applies to |
| `use-colors` | bool | Emit color spans in the output |
| `wireframe-junctions` | flag | Box-drawing junction resolve pass (wireframe + `char-mode="ascii"` only): corners/T-junctions/crossings render from `┌┐└┘├┤┬┴┼─│` instead of a random per-edge glyph |
| `hidden-lines` | `"show" \| "hide"` | Hidden-line removal for wireframe (+ braille) and `ink`. Default `show`. No-op in `solid`, which is already depth-buffered |
| `color-tolerance` | number | Merge adjacent cells into one `<span>` while their colors stay within this redmean distance (range `0`–`765`, not `0`–`255`). Default `0` (off, byte-identical). `NaN`/negative values degrade to `0`; `+Infinity`/`-Infinity` are recognized literally (as strings) and behave the same as the JS surface. No-op under `glyph-output="semantic"` — semantic colors are exact class identifiers, not shaded appearance |
| `glyph-output` | `"visible" \| "semantic"` | Default `visible`. `semantic` needs `sceneManifest` + `dictionary`, which are set as JS **properties** (they are data, not string attributes) |
| `directional-direction` | `"x,y,z"` | Key-light source vector — the direction from the shaded surface *toward* the light. Comma-separated, like `<glyph-mesh position>`. Either this or `directional-intensity` configures the light; the other falls back to its default |
| `directional-intensity` | number | Key-light intensity |
| `ambient-intensity` | number | Ambient fill intensity |
| `auto-size` | flag | Auto-fit `cols`/`rows` to the host's box via ResizeObserver |
| `interactive-downscale` | number | Render at `1/n` resolution while dragging, full detail on release (e.g. `2`). Keeps high-density scenes smooth. Default `1` (off) |
| `shadow` | flag | Enable shadow-map pass. Must be present for any shadows to render |
| `shadow-color` | string | Shadow tint hex color (default `"#000000"`) |
| `shadow-opacity` | number | Shadow darkness 0..1 (default `0.25`) |
| `shadow-lift` | number | Depth bias — prevents self-shadow acne (default `0.05`) |
| `shadow-max-extend` | number | Half-extent of the light-space projection volume (default `2000`) |

`solidWeightRamp` (solid-mode-only font-weight density ramp — see the
[render modes guide](/guides/render-modes/#font-weight-density-ramp-solidweightramp))
is a JS property, not an attribute — it is measurement data
(`{ glyph: string; weight: number }[]`), the same "property, not attribute"
rule `sceneManifest`/`dictionary` use: `document.querySelector("glyph-scene").solidWeightRamp = steps`.

### `<glyph-perspective-camera>` / `<glyph-orthographic-camera>` / `<glyph-camera>`

`<glyph-camera>` is the ergonomic default — an alias for
`<glyph-orthographic-camera>`. All three accept the same orientation
attributes; perspective additionally accepts `distance`, `perspective`, and `stretch`.

| Attribute | Notes |
|---|---|
| `rot-x` | Pitch in degrees |
| `rot-y` | Yaw in degrees |
| `zoom` | CSS pixels per world unit |
| `distance` | Perspective only — pull-back; CSS pixels with CSS perspective, world units only in legacy `perspective="0"` mode |
| `perspective` | Perspective only — CSS-perspective distance in px (default 32000; `0` = legacy orbit) |
| `stretch` | Perspective only |

### `<glyph-mesh>`

Polygon registration. Picks one source in descending precedence: `geometry`
< `src` < explicit polygons (only available via JS property, not attribute).

| Attribute | Notes |
|---|---|
| `src` | URL of OBJ / GLB / glTF / VOX mesh |
| `geometry` | Built-in name from `@glyphcss/core` registry (`cube`, `dodecahedron`, …) |
| `size` | Uniform size for `geometry` |
| `color` | Fill color for `geometry` |
| `position` | `x,y,z` translation |
| `scale` | `s` or `sx,sy,sz` |
| `rotation` | `rx,ry,rz` XYZ Euler degrees |
| `auto-center` | flag — recenter the mesh's bbox center to the origin so it pivots around its own center (center only) |
| `cast-shadow` | flag — this mesh casts shadows onto `receive-shadow` surfaces |
| `receive-shadow` | flag — this mesh displays shadows from `cast-shadow` meshes. Present on both = self-shadow |
| `density` | number — render this mesh at `density`× the scene resolution, in its own `<pre>` (see the [Density & Detail guide](/guides/density/)) |
| `font-size` | explicit cell size (px or CSS length); overrides `density` |
| `line-height` | explicit cell line-height; overrides `density` |
| `transparent` | flag — see-through: doesn't occlude / isn't occluded |
| `mode` | `"wireframe" \| "solid" \| "voxel" \| "ink"` — render this mesh in its OWN mode, in its own `<pre>`, when it differs from the scene's (see the [Density & Detail guide](/guides/density/)). An unrecognized value is ignored |

### `<glyph-hotspot>`

3D-anchored DOM hotspot. Child nodes are positioned at the projected screen
cell every render.

| Attribute | Notes |
|---|---|
| `hotspot-id` | Identifier for handle lookup |
| `at` | `x,y,z` anchor in world space |
| `size` | `w,h` in cells |

### `<glyph-effect-layer>`

Mounts a retained appearance program inside `<glyph-scene>`. The executable
definition is a JavaScript value, so use the atomic property API rather than a
JSON attribute:

```ts
import { GlyphEffects } from "@glyphcss/effects";

const effect = document.querySelector("glyph-effect-layer")!;
effect.configure({
  effect: GlyphEffects.matrixRain,
  params: { glyphs: "HOLA", speedMin: 5, speedMax: 12 },
});
const handle = await effect.whenReady();
handle.params.time = performance.now() / 1000;
```

Attributes `target="surfaces|viewport"`, `blend="over|replace"`, `opacity`,
`order`, and `enabled="false"` override the matching configured option. The
element emits `glyphcss:effect-ready` and exposes `getEffectHandle()`. See
[Glyph Effects](/guides/effects) for the catalog, clocks, and current runtime
boundary.

### `<glyph-orbit-controls>` / `<glyph-map-controls>`

Camera controls. Orbit rotates around the target; map pans across the
target plane.

| Attribute | Notes |
|---|---|
| `drag` | flag — enable drag |
| `wheel` | flag — enable wheel zoom |
| `invert` | flag or number — invert axis multiplier |
| `pitch-range` | Orbit, turntable mode only: `"min,max"` degrees (default `-90,90`), or `"none"` for unrestricted tumbling past either pole |
| `mode` | Orbit-only: `turntable` (default) \| `trackball` — trackball rotates about the screen axis perpendicular to the drag, reaching any orientation including roll |
| `animate-speed` | Orbit-only: auto-rotation speed |
| `animate-axis` | Orbit-only: `x` \| `y` |

### `<glyph-first-person-controls>`

First-person camera (requires a `<glyph-perspective-camera>`). Pointer-lock
mouselook, WASD/arrow move, Space jump, Ctrl crouch.

| Attribute | Notes |
|---|---|
| `look-enabled` / `move-enabled` / `jump-enabled` / `crouch-enabled` | flags — toggle each axis |
| `look-sensitivity` | degrees per pixel (default `0.15`) |
| `invert-y` | flag — invert vertical look |
| `move-speed` / `jump-velocity` / `gravity` | world-unit motion params |
| `eye-height` / `crouch-height` / `ground-z` | world-unit placement |
| `min-pitch` / `max-pitch` | pitch clamp (degrees) |

## Scene-ready handshake

Custom elements register asynchronously. If you need to call imperative
methods on the scene from JavaScript, listen for `glyphcss:scene-ready` on
the `<glyph-scene>` element:

```ts
const sceneEl = document.querySelector("glyph-scene")!;
sceneEl.addEventListener("glyphcss:scene-ready", (ev) => {
  const scene = (ev.target as any).getScene();
  scene.addHotspot({ id: "runtime", at: [0, 0, 1] }, () => alert("clicked"));
});
```

## End-to-end example

```html
<!DOCTYPE html>
<html>
  <head>
    <script type="module" src="https://esm.sh/glyphcss/elements"></script>
    <style>
      glyph-scene { display: block; width: 600px; height: 400px; }
    </style>
  </head>
  <body>
    <glyph-perspective-camera rot-x="65" rot-y="45" zoom="50" distance="3">
      <glyph-scene
        mode="solid"
        cols="120"
        rows="36"
        directional-intensity="1"
        ambient-intensity="0.4"
      >
        <glyph-orbit-controls drag wheel></glyph-orbit-controls>
        <glyph-mesh geometry="dodecahedron" color="#4488ff"></glyph-mesh>
        <glyph-hotspot hotspot-id="top" at="0,0.5,0" size="3,2">
          <span class="badge">top</span>
        </glyph-hotspot>
      </glyph-scene>
    </glyph-perspective-camera>
  </body>
</html>
```

## See also

- [React API](/api/react) — same elements, JSX form
- [Vue API](/api/vue) — same elements, idiomatic Vue
- [Headless API](/api/headless) — the imperative factory the elements wrap
