# @borkiss/tre — recipes

Copy-paste starting points for every mode. All standalone recipes import from
`@borkiss/tre/core` (peer dep: `three` only). Reference: /api.md.

The canvas in every recipe needs a CSS size, e.g.
`<canvas id="tube" style="width:100%;height:400px"></canvas>`.

## 1. Image → colour ASCII

```ts
import { createAsciiArt } from '@borkiss/tre/core'

const img = new Image()
img.src = '/photo.jpg'
const art = createAsciiArt({ canvas, source: img, fit: 'cover' })
art.frame.colorize = 1     // glyphs take the photo's colours
art.frame.cellPx = 7       // density
```

## 2. Webcam mirror

```ts
import { createAsciiArt, createWebcamSource } from '@borkiss/tre/core'

const cam = await createWebcamSource()          // ask on a user gesture
const art = createAsciiArt({ canvas, source: cam.video })
canvas.style.transform = 'scaleX(-1)'           // mirror like a selfie
// later: cam.stop()  — releases the camera
```

## 3. Video file

```ts
const video = document.createElement('video')
video.src = '/clip.mp4'
video.muted = true; video.loop = true; video.playsInline = true
await video.play()
createAsciiArt({ canvas, source: video, fit: 'cover' }).frame.colorize = 0.8
```

## 4. A 2D canvas you keep redrawing (charts, generative art)

```ts
const src = document.createElement('canvas')   // draw into this any way you like
src.width = 320; src.height = 240
const ctx = src.getContext('2d')!

const art = createAsciiArt({
  canvas,
  source: src,
  onFrame: (_frame, t) => drawMyThing(ctx, t),  // engine re-uploads it each frame
})
```

## 5. Live three.js scene

```ts
import * as THREE from 'three'

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(42, 4 / 3, 0.1, 60)
camera.position.z = 3.5
const mesh = new THREE.Mesh(
  new THREE.TorusKnotGeometry(1, 0.34, 240, 28),
  // output GRAYSCALE brightness — the pipeline owns colour (tint / colorize)
  new THREE.MeshBasicMaterial({ color: 0xffffff }),
)
scene.add(mesh)

createAsciiArt({
  canvas,
  source: { scene, camera, update: (t) => { mesh.rotation.y = t * 0.4 } },
})
```

Better than MeshBasicMaterial: a lambert+rim luminance shader — see
`SubjectMaterial` in the full entry, or view-source on the site's tubes.

## 6. The looks — one-liners on `art.frame`

```ts
art.frame.tint.set('#ffb454')   // amber phosphor (P3); '#52ff7a' = green P1
art.frame.colorize = 1          // colour from the source instead of tint
art.frame.persistence = 0.7     // motion smears into phosphor trails
art.frame.glitch = 0.5          // datamosh rows + flicker
art.frame.scramble = 0.3        // cells swap to garbage glyphs
art.frame.jitter = 0.4          // degauss shake
art.frame.power = 0             // CRT off (animate 0↔1 for the collapse)
art.setRamp(' ░▒▓█')            // any string, dark → bright; RAMPS has presets
```

## 7. Dot matrix — marks instead of glyphs

```ts
const art = createAsciiArt({ canvas, source: mySource, style: 'dots' })
art.frame.pulse = 0.35          // a breathing wave rolls through the grid
art.frame.markScale = 1.1       // dot size (0.4 sparse .. 1.5 touching)
art.frame.spin = 0.4            // marks twirl in place, per-cell phase
art.frame.style = 'hex'         // swap live: dots · rings · blocks · cross · diamond · hex · slash · glyphs
```

Marks are procedural (no font atlas), live in square cells and size themselves
by luminance — halftone dots, hollow rings, LED blocks, crosses, diamonds,
hexagons, diagonal slashes. Everything else (tint, colorize, persistence,
glitch, power) works unchanged.

## 8. Digital rain

```ts
import { createAsciiArt, RAMPS } from '@borkiss/tre/core'

const art = createAsciiArt({ canvas, source: mySource, ramp: RAMPS.katakana })
art.frame.rain = 0.75           // falling streaks, one per column, endless
art.frame.flicker = 0.2         // per-cell flutter — the sign is dying
art.frame.persistence = 0.3     // streak heads leave phosphor trails
```

`rain` is a max() overlay: it lights glyphs even where the scene is black, so
it works over any source — including none. `ripple` (concentric) and `pulse`
(diagonal) are sibling waves that modulate brightness instead.

## 9. Audio-reactive

```ts
import { createAsciiArt, audioReactor, audio } from '@borkiss/tre/core'

await audioReactor.connectMic()               // or connectElement(audioEl)
createAsciiArt({
  canvas,
  source: mySource,
  onFrame(frame) {
    frame.glow = 0.8 + audio.beat * 1.2       // beats flare the tube
    frame.persistence = 0.3 + audio.level * 0.5
  },
})
```

## 10. Export a still

```ts
const dataUrl = art.snapshot()                // PNG data URL, current frame
```

## 11. React (R3F)

```tsx
import { Canvas } from '@react-three/fiber'
import { AsciiRenderer } from '@borkiss/tre'

<Canvas frameloop="demand" dpr={[1, 1.5]} gl={{ antialias: false }}>
  {/* your scene — output luminance */}
  <AsciiRenderer onFrame={(f) => { f.persistence = 0.5 }} />
</Canvas>
```

## 12. Scroll-driven story (the full stack)

```tsx
import { ScrollStage, defineActs, createActTimeline, AsciiRenderer, audio } from '@borkiss/tre'

const story = defineActs([
  { key: 'boot', weight: 0.6, hue: 0.33 },
  { key: 'flight', weight: 1.4, hue: 0.6 },
])
const params = { hue: 0.33 }
const tl = createActTimeline(story, (tl, act, at, duration) =>
  duration === 0 ? tl.set(params, { hue: act.hue }, at)
                 : tl.to(params, { hue: act.hue, duration }, at))

<ScrollStage timeline={tl} energySource={() => audio.level}>
  <Canvas frameloop="demand" dpr={[1, 1.5]} gl={{ antialias: false }}>
    <AsciiRenderer onFrame={(f) => f.tint.setHSL(params.hue, 0.72, 0.62)} />
  </Canvas>
</ScrollStage>
```

Sharp act-local envelopes (glitch humps, power cuts) don't go through GSAP —
compute them from `story.actLocal(progress.value, i)` inside `onFrame`.
Reference consumer: https://tre.borkiss.net/deep-field (view-source →
the `demo/` folder of the package repo).

## Performance notes

- Defaults already cap fps at 60, clamp dpr to 1.5 and pause offscreen tubes.
- Weak GPUs / mobile: `createAsciiArt({ lowCost: coarseLowPower(), ... })`.
- Many tubes on one page: create them lazily (IntersectionObserver) — WebGL
  contexts are the scarce resource, ~8–12 is a practical ceiling.
- Never allocate inside `onFrame`; mutate `frame` fields and reuse objects.
