Sci-fi Hologram HUD 科幻全息界面

Volt · Spec Scan·39s·Lemo-Opuscarby Lemomo

An object is scanned into a glowing wireframe. Target boxes lock onto its parts; each opens in a local exploded view, a leader pulls out, and its number rolls from garbage glyphs into the real value. It may light up, once, into a solid hologram.

The demo: An e-bike is scanned into a hologram; target boxes lock onto the battery, motor and brakes, and each spec rolls into place.

Product spec walkthroughsLaunch & crowdfunding videosTrade-show screens
Style prompt · STYLE.md

An object is scanned into a glowing wireframe. Target boxes lock onto its parts; each opens in a local exploded view, a leader pulls out, and its number rolls from garbage glyphs into the real value. It may light up, once, into a solid hologram. References (grammar only): the Iron Man / Avengers HUDs (target boxes, callouts, rolling numbers, layered depth); the Oblivion interfaces (one colour, hairlines, space); CAD exploded views. Copy none of their layouts, icons, fonts or colours.

A scene style: it does a practical job (a spec walkthrough, a teardown, a loop screen) for any object with parts. See §11 for the jobs.

1. Essence, and what it is not

The subject is always a line wireframe made of light above a round projector pad on a near-black stage, never a rendered object. Everything reads as scanned data: a scan plane grows the model, parts pull apart along their assembly axes, numbers are computed on screen.

Three depth layers are always present: a background dot grid (slow parallax), the subject with its pad and floor, and the foreground HUD (corner brackets, rulers that parallax faster, status text).

Not a type-driven keynote (dark-keynote), not a drafting sheet (blueprint), not a product render (glass-product). The subject stays a wireframe; it may gain translucent faces once, and even then the lines stay on top.

2. Materials & rendering

  • Stage: near-black tinted by the hue, a radial lift behind the subject, a fading dot grid, a strong vignette.
  • Projector pad in true perspective: concentric rings (one bright, one dashed), ticks, faint rays; it rotates slowly and spins with any turntable move.
  • Wireframe: additive ~1.3 px strokes in a few alpha levels set by depth fog (near bright, far ~25 %), plus two low-res glow passes.
  • Hot edges (near-white of the hue) only for what matters now: the scan front, inner pieces flagged hot when opened, a light wave, a part being pinged.
  • Real CAD topology: tubes are rings plus longitudinal lines. Every part is physically connected: a floating piece is noticed first.
  • Solid hologram: faces filled with a screen-space Fresnel × a scrolling interlace; lines thicken as it ignites; a light wave climbs the model. A beam (one path, nonzero winding) with faint rays and dust.
  • HUD furniture: corner brackets, parallaxing rulers, subject // mode top left, a live status line top right, telemetry at the bottom.
  • Text never sits on the wireframe: every text block has its own dark plate (translucent near-black, 1 px border, corner ticks); the camera still keeps the subject clear of text.

3. Colour logic

  • One cold hue plus one warm accent, both parameters (e.g. mint/orange, violet/lime, ice-blue/red; choose from the subject).
  • The hue carries lines, HUD and fills. The accent is spent on locked values only: a number turns accent-coloured at the frame it becomes true. Nothing else ever uses it.
  • White is heat: near-white edges and one-frame flashes mark what is active. Everything at rest sits in the hue's alpha levels.
  • One object may keep its own colour (the single-colour exception, keep), for a brand mark, never for decoration.

4. Type & subtitles

  • Rajdhani, all caps: Light for big numbers and the product name (wide tracking), Medium/SemiBold for labels. Share Tech Mono for micro data (coordinates, counters, zoom).
  • Numbers are monospaced so rolls never jitter; the decimal point is a small square (a glowing "." reads as a comma).
  • Subtitles are HUD: a dark plate with corner brackets and "voiceprint" bars moving with the speech, near-white Rajdhani Medium, bottom centre; in just before the voice, hold ≥ max(1.8 s, speech + 0.6 s).
  • The title is part of the scene (no title card): the name rolls in like a value, then code, category and tagline.

5. Motion quality

  • Everything on ones at 24 fps. Only glyph scrambling steps, every frame.
  • Target lock: oversized, rotated 45°, faint; snaps to fit in ~0.2 s (cubic ease-out), flashes white 3 frames on the downbeat.
  • Explode along each piece's assembly axis: the shell slides along its own axis, the contents stay in place and glow. Open over a beat or two, close fast.
  • Leader: anchor ring → diagonal → horizontal to the card in ~0.5 s. Rolling value: characters lock right-to-left and turn accent on the downbeat with a one-frame flash.
  • Chip dock: a finished value slides into a side rail and stays until the end.
  • The camera follows the box: cubic ease, starting before the next lock, real motion blur only inside the whip (sub-frames, 180° shutter); HUD stays sharp.
  • No cuts or fades inside the scene: only the scan plane (scanning in or erasing) and a loupe iris. A glitch of three frames at most once, on the biggest change.

6. Camera grammar

An orbit camera (yaw, pitch, distance, narrow fov) with a screen position for the target, so the subject can sit beside text. A vocabulary, not a route. Opening and ending come from the topic.

Move Expresses Can serve
High 3/4, slow orbit craning down Examination; a scan plane reads as an ellipse a scan-in; a survey
Top-down plan view Layout, order a parts inventory; an assembly sequence
Eye-level profile, locked The object as it stands in use a height; a before/after
Push-in, then drift, 3/4 This part matters; internals come towards the lens the key spec; a flaw
Whip following the target box Attention jumps to the next part a list of parts
Orbit around a part's own axis Axial structure only reads in rotation rotors, stacks, hinges, lenses
Lateral dolly along a long part Length, a path a beam; a cable run
Pull back + loupe (frame in frame) Where (wide) and what (loupe) small parts; a sensor
Crane to eye level or back up Status change: the object stands up or settles a transformation; scale
Turntable 360° Every side a whole object; a symmetry
Nearly static, slow dolly Reading time without a dead frame a spec sheet; a checklist

Framing: the subject is ≥ ⅓ of frame height at every key moment. Cards on one side, the chip rail on the other, subtitles bottom centre, a loupe above them: never colliding. Shorten explode vectors rather than fly a piece into text.

7. Sound palette

  • Music first, on a grid (a bar = one information beat); locks land on downbeats, checked against the score.
  • Synth palette: saw/square arpeggio with ping-pong delay (scooped 1–4 kHz for the voice), square pulse, 8th bass, synth hats and claps, detuned saw pads.
  • Density follows information. Tools to pick from, in any order (most films need two or three): half-time for reading time; one beat of true digital silence (nothing, not even UI blips); a drop (808-style glide + wide saw chord) for the biggest change; a layer added or removed per part; deceleration by longer note values; a key change; a single sustained tone under a long hold.
  • Foley follows the material: light = sines and FM blips in the key; machines = servo slides, pneumatic puffs, hydraulic hiss, metal tinks (J-cuts); a lock = thump + clack + two-tone beep; rolls tick at frame rate. Ambience: projector hum (40/80/120 Hz) and room tone.
  • Voice: calm, confident. Duck music and foley with a held envelope (no pumping between words); lock transients escape. −14 LUFS.

8. Native moves

Each move serves information, not story. A menu: use the ones your film needs.

  • Scan growth = "this is real data." Fits content like: a coffee machine; a satellite; an architectural model.
  • Target lock = "look here now." The box is the camera. Fits content like: a car's sensors; a laptop's ports; a robot arm's joints.
  • Local explode = "why the number is true." Fits content like: the burr set of a grinder; the layers of a running shoe sole; the elements of a camera lens.
  • Rolling digits = the payoff beat. Fits content like: a brew temperature; a tolerance in microns; a price.
  • Chip accumulation = reading time. Fits content like: 3–5 facts that must all be read.
  • Loupe = two scales at once. Fits content like: a screw inside a watch; a chip on a board; a valve in an engine.
  • Solid hologram + beam = "the whole is ready." Once, if at all. Fits content like: a launch reveal; a finished assembly; a v2 replacing v1.

9. Pitfalls of the medium

  • Target boxes balloon → rest pose, skip internal pieces, long attachments box:false.
  • Long diagonal leaders point at the wrong part → ring on the part body, flash the part.
  • Two parallel boxes moving don't read as an explode → shell slides on its axis, contents stay and glow.
  • A part built from generic boxes doesn't read as that part → give it its characteristic silhouette.
  • Voice masked by foley, not music → duck foley too, hold the duck through word gaps.
  • UI blips inside a silence break it → check cues against silence windows.
  • A whip written inside the next segment plays as a hard cut → start it in the previous one.

10. Engine

demo/engine/holo.js (wireframe engine: projection, scan, explode, Fresnel faces, glow, pad, scan plane, normalizeModel, modelFromPath to extrude any 2D path), demo/engine/hud.js (theme, plate, target box, leader, rolling text, spec card, chip, marker, beam, subtitle, chrome, glitch), demo/models/mkmodel.mjs (primitives: tube, ring, disc, box, wheel, cells…). Models are normalised on load, so cameras work for any object. API, model format and an example: DEMO.md.

11. Variation space

You decide the object, its model, which parts and in what order, the opening, the ending, the camera path, the colours, the pacing and the music. Use cases are grammar for the order and duration of information; build your own timeline from the user's facts.

Use case Information order Hold per layer Length
Spec walkthrough Name + tagline → one spec per part (label, value, unit, detail) → weight / price → CTA Title ≥ 5 s; value ≥ 2.5 s, then docked; final sheet ≥ 3 s 30–40 s, 2–5 parts
Loop screen Name → 3–5 specs → CTA, no voice; last frame = first 3 s per spec, 5 s final 20–30 s loop
Teardown Part → what it does → one number; internals only when opened 4–6 s per part 40–60 s
Upgrade Part → old value (dim) → new value (accent); old part scan-erased, new one scanned in 3 s per upgrade 20–30 s
Assembly order Step number → part name; parts snap home one by one one bar per part 20–40 s

All far from our demo:

  • Structures: a loop screen that never names a product and cycles five sensors; a teardown one layer deeper per shot until the core is a single hot piece; an upgrade where two versions share the pad and every number rolls from old to new.
  • Openings: a part first (one component locked in close-up; the rest scans out from it); a flat outline (a 2D silhouette extrudes into the wireframe, modelFromPath); plan view (top-down, the pad as a dial, the object arriving as its footprint).
  • Endings: one value at eye level (the wireframe dims around the single number that matters); knolling (parts fly into a labelled top-down layout); power-down (edges cool, HUD panels switch off until only the pad glows).

Swapping demo/content.json and rebuilding is a technical check that the engine re-flows, not a way to make a film.


How our demo was made (story, shots, score, end card, build, content fields): DEMO.md. Read it after your treatment exists.

Director's notes for the demo film

One example among many. Don't reuse its story, arc, shots, props or timings.

Demo: Volt · Spec Scan (39 s: a 34 s scene plus a 5 s end card) · hologram-hud.mp4 · source in demo/

Story & structure

The spec walkthrough of a fictional city e-bike, the first use case of STYLE.md §11: a hardware product is scanned into a glowing wireframe, three parts are locked one at a time (battery, motor, brakes), and at the end the wireframe lights up into a solid hologram and settles into a spec sheet you could use as a poster.

Shot structure: scan-in → title → 3 × (lock → camera move → local explode → leader → value) → regroup → solid-hologram turn → spec-sheet lockup → end card. Part 1 gets 3 bars, the others 2 bars each. Each value stays on the big card ≥ 2.5 s, then docks in the left rail until the end, so every spec is on screen for 10–25 s.

Signature shot: part 1 as one move. Lock, push, the cover slides, the cells glow, the leader pulls out, and "80 km" rolls amber.

Shots

Beat Camera Why
Hook (0–2.6 s) 3/4 front, pitch 14°, slow orbit yaw 57° → 24° while it cranes down From above, the scan plane reads as an ellipse rising. Then it drops to near eye level and leaves room for the title on the left. Frame 1 already shows the plane at wheel height
Part 1 (slow) Push-in 2.85 → 1.6 (1.5 s ease-out), then drift The first spec gets the most time
Part 2 Whip (following the box) + orbit around the hub axis, yaw −11° → 46° An axial explode only reads when you orbit around the axis
Part 3 (fast) Pull back to full + loupe (×4.3, its own counter-orbit) Brakes are small: both scales on screen at once
Regroup Pull back + crane down to pitch 0 A low angle makes the object stand up before the light-up
High point Turntable 360° + crane up 0 → 13° + slight push The only moment the viewer sees every side
Lockup Nearly static, a slow dolly 3.5 → 3.4 The viewer reads
End Scan-erase top-down, then the end card Mirrors the hook

Why this route, for this product: the hook's orbit craning down made the rising scan plane read as an ellipse; the regroup pulled back and craned down to eye level so the bike stood up before the light-up; the turntable was the only moment every side showed; the ending scan-erased top-down to mirror the hook. Each is one choice from STYLE.md §6, not the style's default opening or ending. The title rolled in after the scan and returned in the same place in the final lockup; the three-frame glitch was spent at ignition.

Framing numbers: fov 0.6. Big cards at x ≥ 1240, the rail at x 110–470, subtitles fixed at the bottom centre (baseline y 962), the loupe above y 580. In the wide loupe shot the target sits at x 0.49 → 0.475 of the frame.

Motion numbers: the target box starts at 1.9× the rest-pose bounding box, at 30 % alpha, snaps in 0.22 s; tag TGT 0N · LOCK. Explodes: the battery cover slides along the down tube while the cells stay and turn hot; the motor splits axially (cap / rotor / stator / cap) while rotor and stator counter-rotate; the brake caliper halves part on either side of the rotor, pistons separating less than the body so they appear to extend. Open over 1 beat (part 1: 2 beats), close over 0.5 s. Rolling value: 0.5 s (part 1: 1 s). Chip dock: card fades 0.35 s, chip slides x 140 → 110; rail step min(112, 410 / (n − 1)) px, so 5 chips fit. Whip: 0.5 s cubic in-out, starting 0.45 s before the next lock; 5 sub-frames over 1/48 s.

Score structure

  • Score arc: sparse on the first spec, a layer added per part, deceleration before the high point, one beat of true silence, the drop at ignition (the loudest moment), half-time under the lockup.
  • 120 BPM (1 bar = 2 s); tools/cuecheck.py checks every key event against music/score.json (all on the 1/16 grid, 0 ms offset).
  • Original numpy synthesis, D minor with Dorian colour; chords Dm9 → B♭maj7 → Fmaj9 → G6. A D1 square pulse on quarters.
  • No drums on part 1, + hats on part 2, + claps and the arpeggio up an octave on part 3. Regroup decelerates (16ths → 8ths → one quarter gliding down). One beat of true silence. Drop at ignition (808-style sine glide + 7-voice wide saw chord). Half-time under the lockup. The arpeggio is removed note by note under the end card.
  • Silences: the half beat before the first lock (only the room hum remains; the lock hit is the first sound after it) and one full beat of true zero before ignition.
  • Detect blips D–F–A; amber confirm = D6 + A6 + E7. J/L-cuts: the motor whine starts 0.4 s before the whip lands on the motor, the hydraulic hiss 0.4 s before the brake lock; hum and chord tail carry over the scan-erase into the end card.
  • Voice: Kokoro af_heart, speed 1.0 (af_nicole was too breathy and slow: 7.0 s against 4.4 s for the same line). Music ducks −16.5 dB and foley −15 dB under the voice, with a 0.5 s max-filter hold; only the first 0.2 s of lock / confirm hits escape.

Palette & props

  • Stage hsl(hue+12, 70%, 2.4%) with a radial lift hsl(hue+6, 65%, 7%); 48 px dot grid (parallax 0.2); vignette 0.5.
  • Pad rings at r = 0.7 / 1.05 (bright) / 1.12 (dashed) / 1.45 / 2.1, 120 ticks (every 10th long), 24 radial rays, rotating 0.12 rad/s.
  • Wireframe: lighter strokes 1.3 px in 7 alpha levels; glow ¼-res blur 3 px ×0.85 and ⅛-res blur 4 px ×0.7; colour hsla(hue, 100%, 50–80%, a); hot edges hsla(hue, 70%, 92%), the 7 cm under the scan front.
  • Solid: Fresnel 0.3 + 0.7·(1 − facing)^1.6 (facing = projected area / edge product), interlace 2 px full / 2 px at 38 % scrolling 1 px per frame; line width ×1.5 at ignition easing to ×1.12; light wave ±7 cm at ignition and on the chord change. Beam gradient 10 % → 0, 26 rays, 70 dust motes.
  • Colour: hue 188 (cyan), accent 38 (amber); the alternate content uses 204 / 18 (blue / orange).
  • Type: Rajdhani 300 for big numbers and the product name (26 px tracking), 500/600 labels (2–3 px tracking); Share Tech Mono 20–22 px. Plates rgba(1,8,12,.72), border 28 %, 10 px corner ticks (plate() in hud.js).
  • HUD: 46 px corner brackets 44 px from the edge; rulers tick every 24 px (every 5th long). Top-left: product // SPEC SCAN. Status: SCANNING 042% → SCAN COMPLETE · 3 SYSTEMS → TARGET 2/3 · MOTOR → HOLO SOLID · 360° → SPEC SHEET · READY.
  • Subtitles: plate rgba(1,8,12,.62), two diagonal cyan brackets, four voiceprint bars, Rajdhani Medium 38 px, wrap 1180 px; in 0.1 s before the voice, out 0.12 s before the next line.
  • The e-bike model: tube frame, wheels with tyre (3 rings + cross ribs), rim, spokes and hub; battery in the down tube; hub motor; disc brake with caliper. The caliper needed an arc-shaped body hugging the rotor edge, round piston faces, pads following the rotor curve and vent holes on the rotor.

End card

After the lockup: film title, SCI-FI HOLOGRAM HUD, Lemo-Opuscar, LemoLab × Claude Opus 5.5 and credits, each line rolling in. The LemoLab sign-off belongs to this library's demo only; a user's film carries no LemoLab credit and no copy of this card.

Build notes

styles/hologram-hud/demo/
  content.json          all text, numbers, colours, model path, voice lines   content_alt.json   the swap test (drone)
  engine/holo.js        wireframe engine (projection, scan, explode, Fresnel faces, glow, pad, scan plane, normalise, modelFromPath)
  engine/hud.js         HUD layer (target box, leader, rolling text, spec card, chip, marker, beam, subtitle, chrome, glitch)
  models/mkmodel.mjs    primitive builder (tube, ring, disc, box, saddle, fender, wheel, cells, stator, rotor, caliper…)
  models/gen_volt.mjs   the e-bike    models/gen_kite.mjs   the drone
  film.js               timeline + cameras + states (only calls the engine)    frames.js   engine demo scene (?scene=star)
  music/score.py        original score from timeline.json    mix.py   UI + mechanical foley + ambience + voice + ducking
  tools/                make_lines.mjs export.mjs cuecheck.py final_asr.py
  1. Write the model generator (parts → pieces with explode vectors; internal, hot, box:false, anchor, center, tagDir), then content.json.
  2. tools/export.mjs dumps events.json / timeline.json / subtitles. Hand timeline.json and the cue map to a music sub-agent while you animate.
  3. Review stills (node core/render/still.mjs <demo> t… [--q content=…]) and 1 s contact sheets, twice, plus strips around every lock, whip, silence and ignition.
  4. sh demo/build.sh rebuilds everything: model → TTS → whisper → export → score → cuecheck → mix → srt → render (~45 s for 936 frames with 2 workers; whip frames cost 5×) → mux (−14 LUFS, grain 0) → final whisper check.

Pitfalls met in this build:

  • Scrolling the interlace with setTransform(…, offset) and then destination-in cleared everything outside the shifted rect → offset % patternHeight, or the fills vanish after the first second.
  • The brake hose inflated the target box → box:false.
  • Hotspot labels on long diagonal leaders read as pointing to the wrong part → ring on the part body, chip beside it, flash the part.
  • A whip written inside the next segment played as a hard cut → start it in the previous segment and check a 0.1 s frame strip.
  • The light with no bracket and the saddle with no clamp; in the swap model, the drone's landing legs started outside the hull (belly mounts and fold hinges at the arm roots fixed it).
  • Two parallel boxes (cover and cells both moved) didn't read as an explode.
  • In-band (300–4 kHz), rolls, servos and the confirm chord were only 3 dB under the voice.
  • The beam drawn from separate shapes gave dark wedges or double-bright seams → one path with three subpaths, nonzero winding.
  • Scan-erase first clipped only edges, and translucent faces lingered.

content.json fields

Every word, number, colour and the model come from demo/content.json; the code holds no copy text. Swapping it re-flows the timeline, music, cues and rail spacing, which makes it a good technical check of the engine. It is not how a user's film is made: a user's film gets its own treatment, order, camera path and timeline (STYLE.md §11).

Field Type Allowed If out of range
product string 1–10 chars Longer names still roll, but 176 px type will overflow the 410 px column: keep it short or lower the size in titleBlock
model_code, category string ≤ 30 chars together Shown in mono 22 px on one line
tagline string ≤ 60 chars Auto-wraps at 420 px and shrinks to 20 px
model path a model JSON (below) Any size: it is normalised. Parts named in callouts[].part must exist
hue, accent 0–360 any The whole film re-themes
voice {id, speed} a Kokoro voice Check with whisper
intro_vo, outro_vo, callouts[].vo string intro ≤ 5 s spoken; part 1 ≤ 4.8 s; other parts ≤ 3.5 s Subtitles are clamped to the next line; longer lines overlap the next lock
*_vo_asr / callouts[].vo_asr string optional What whisper should hear (digits etc.)
callouts[] array 2–5 items: part, label (≤ 24 chars), value (≤ 6 chars), unit (≤ 4), detail (≤ 40, optional), vo Part 1 gets 3 bars, the others 2 bars each. More than 5 won't fit the rail
footer.weight, footer.price {label, value, unit} value ≤ 7 chars Auto-fits down to 40 px
footer.cta string ≤ 34 chars Auto-fits down to 14 px
film_title, credits[] strings End card

Parts in the model can set tagDir: -1 so the lockup hotspot chip sits to the left of its ring.

The check, from the repo root (voice, whisper check, score, cue check, mix, subtitles, render and mux for the other content):

CONTENT=content_alt.json NAME=kite sh styles/hologram-hud/demo/build.sh

Outputs go to demo/out/kite/ (kite.mp4, kite.srt, poster.jpg). The swap test (a four-part folding drone in blue / orange) came out at 43 s, 4 parts, −14.0 LUFS, 0 cue mismatches; stills demo/stills/alt_title.jpg, alt_signature.jpg, alt_lockup.jpg. Preview content without building: node core/render/still.mjs styles/hologram-hud/demo 12 --q content=<file>.json.

Minimal content example (two parts):

{ "product": "ORBIT", "model_code": "S1", "category": "Smart speaker", "tagline": "Room-filling sound.",
  "model": "models/orbit.json", "hue": 170, "accent": 40,
  "voice": { "id": "af_heart", "speed": 1.0 }, "intro_vo": "This is Orbit.",
  "callouts": [
    { "part": "driver", "label": "Woofer", "value": "4", "unit": "in", "vo": "A four inch woofer." },
    { "part": "mics", "label": "Far-field mics", "value": "6", "unit": "", "vo": "Six microphones hear you across the room." } ],
  "footer": { "weight": { "label": "Weight", "value": "1.2", "unit": "kg" }, "price": { "label": "From", "value": "199", "unit": "USD" }, "cta": "Available now" },
  "outro_vo": "Orbit. Available now.", "film_title": "Orbit · Spec Scan", "credits": ["…"] }

Engine reference

The engine draws any shape in this style. Give it a wireframe (vertices, edges, optional quads), or build one from a 2D path with modelFromPath. For your own object, write a generator with the primitives in models/mkmodel.mjs (or convert a CAD export into the format below).

engine/holo.js

Function Purpose
loadModel(url, {normalize=true, diag=2.3117}) Load a model JSON, prepare typed arrays, and normalise it to the stage size (bounding-box diagonal) so cameras work for any product
prepModel(json) / normalizeModel(m, diag) The same for in-memory models
modelFromPath(pts2d, {depth, id, slices, explode}) Extrude any closed 2D path into a wireframe body with side faces
makeCamera(cam, W, H) {project(x,y,z,out)} for cam {target, yaw, pitch, dist, fov, cx, cy, roll}
new Holo(W,H).draw(ctx, model, cam, opts) Draw with glow. opts: hue, spin (turntable), scan:{y,band} (grow up to y), scanDown:{y} (erase down to y), explode:{partOrPiece:0–1}, angle:{piece:rad} (spin about piece.axis), solid:0–1, wave:{y,w}, focus+dim, flash:{part:0–1}, keep:{piece:'#hex'} (the single-colour exception), lineWidth, gain, glow, only. Returns {bbox:{part:[x0,y0,x1,y1]}, anchor:{part:[sx,sy,z]}}
drawPad(ctx, cam, W, H, {hue, rot, alpha}) / drawScanPlane(ctx, cam, W, H, y, {hue, r, rot, alpha}) The projector pad and the scan plane

engine/hud.js: theme(hue, accent), background, vignette, chrome, plate(ctx,T,x,y,w,h,a) (dark text plate), targetBox(ctx,T,bbox,k,{flash,tag,lockK}), leader(ctx,T,from,elbow,to,prog,{pulse}), rollText(str,k,frame), monoText, specCard(ctx,T,x,y,{label,value,unit,detail},{reveal,roll,detail,frame,flash}), chip, marker(ctx,T,p,label,a,pulse,dir), beam(ctx,T,project,a,t,{y1}), subtitle, glitch, fitFont, wrap.

Minimal example: a warm-orange #D97757 four-point star with a cursor tail, scanned in and then locked (demo/frames.js, ?scene=star):

import { Holo, modelFromPath, normalizeModel } from './engine/holo.js';
import * as U from './engine/hud.js';
const P = []; for (let i = 0; i < 8; i++) { const a = Math.PI/2 + i*Math.PI/4, r = i % 2 ? .16 : .5; P.push([Math.cos(a)*r, Math.sin(a)*r + .6]); }
const star = modelFromPath(P, { depth: .12, id: 'star' });
const tail = modelFromPath([[.62,.1],[.7,.1],[.7,.55],[.62,.55]], { depth: .06, id: 'caret' });
const m = { parts: [...star.parts, ...tail.parts] }; m.byId = Object.fromEntries(m.parts.map(p => [p.id, p])); normalizeModel(m, 1.6);
const T = U.theme(188, 38), cam = { target:[0,.55,0], yaw:.6, pitch:.18, dist:2.6, fov:.6, cx:960, cy:540 };
const info = new Holo(1920,1080).draw(ctx, m, cam, { hue: T.hue, scan: { y: .8 }, keep: { star: '#D97757' } });
U.targetBox(ctx, T, info.bbox.star, 1, { tag: 'TGT 01 · LOCK' });

Model data format (one JSON file, metres, y up, x = the product's front, z = towards the viewer's side; any scale works, because it is normalised on load):

{ "name": "VOLT CE-01",
  "parts": [                                   // part = one thing a callout can target (battery, motor, brakes…)
    { "id": "brakes",                          // referenced by content.json callouts[].part
      "anchor": [0.527, 0.416, 0.09],          // hotspot: where leader lines and the lockup ring land (ON the part's surface)
      "center": [0.524, 0.428, 0.08],          // optional: what the close-up / loupe camera looks at (defaults to anchor)
      "tagDir": -1,                            // optional: lockup number chip left (-1) or right (1) of the ring
      "pieces": [                              // piece = a rigid sub-assembly that moves on its own in the explode
        { "id": "brake-caliperA",
          "v": [x0,y0,z0, x1,y1,z1, …],        // vertices, flat
          "e": [0,1, 1,2, …],                  // edges: index pairs into v (the wireframe lines)
          "q": [0,1,5,4, …],                   // optional quads: 4 indices each (the translucent solid-hologram faces + Fresnel)
          "explode": [0, 0, 0.10],             // offset at explode = 1, along the assembly axis
          "axis": { "c": [x,y,z], "d": [0,0,1] }, // optional spin axis (rotors, fans)
          "internal": true,                    // optional: hidden until the part opens (cells, stator)
          "hot": true,                         // optional: glows near-white while open (the thing that makes the number true)
          "box": false }                       // optional: excluded from the target-box bounds (long hoses, cables)
      ] } ] }

Rules: every piece must physically touch its neighbour. Tubes are rings plus longitudinal lines. Keep the total around 5k vertices / 6k edges for fast renders. Give the part that carries the key number a hot inner piece and a shell that slides along its own axis.