// Viji Artist API - Global Type Definitions
// All types are placed inside declare global {} because this file uses export {}
// for top-level await support, which makes it a module (where top-level declarations
// would otherwise be module-scoped, not global).

declare global {
  /**
   * Real-time audio analysis for the main audio stream (`viji.audio`).
   * Provides volume, frequency bands, beat detection (with triggers, events, BPM),
   * spectral features, and access to raw FFT / waveform data.
   *
   * All numeric outputs are zeroed when `isConnected` is `false`.
   */
  interface AudioAPI {
      /** `true` when an audio source is connected; otherwise all numeric fields read as `0` and helper methods return empty data. */
      isConnected: boolean;
      /** Overall loudness across the stream. */
      volume: {
          /** Instantaneous RMS volume in 0..1. */
          current: number;
          /** Instantaneous peak amplitude in 0..1. */
          peak: number;
          /** Volume with a 200ms decay envelope; rises fast, falls smoothly. Use for flicker-free animations. */
          smoothed: number;
      };
      /** Energy in five fixed frequency bands, plus a smoothed companion (~150ms decay) for each. All in 0..1. */
      bands: {
          /** Instant low-band energy (20-120 Hz, bass/kick range). 0..1. */
          low: number;
          /** Instant low-mid energy (120-400 Hz). 0..1. */
          lowMid: number;
          /** Instant mid energy (400-1600 Hz, vocals/instruments). 0..1. */
          mid: number;
          /** Instant high-mid energy (1600-6000 Hz, cymbals/hi-hats). 0..1. */
          highMid: number;
          /** Instant high-band energy (6000-16000 Hz, air/brilliance). 0..1. */
          high: number;
          /** Smoothed `low` (~150ms decay). 0..1. */
          lowSmoothed: number;
          /** Smoothed `lowMid` (~150ms decay). 0..1. */
          lowMidSmoothed: number;
          /** Smoothed `mid` (~150ms decay). 0..1. */
          midSmoothed: number;
          /** Smoothed `highMid` (~150ms decay). 0..1. */
          highMidSmoothed: number;
          /** Smoothed `high` (~150ms decay). 0..1. */
          highSmoothed: number;
      };
      /** Beat detection: fast/smoothed energy curves, single-frame boolean triggers, raw event list, and BPM tracking. */
      beat: {
          /** Kick energy curve in 0..1 with a 300ms decay. Peaks on each detected kick. */
          kick: number;
          /** Snare energy curve in 0..1 with a 300ms decay. */
          snare: number;
          /** Hi-hat energy curve in 0..1 with a 300ms decay. */
          hat: number;
          /** Energy curve for any beat type in 0..1 with a 300ms decay. */
          any: number;
          /** Smoothed `kick` curve in 0..1 with a 500ms decay. Use for ambient pulses. */
          kickSmoothed: number;
          /** Smoothed `snare` curve in 0..1 with a 500ms decay. */
          snareSmoothed: number;
          /** Smoothed `hat` curve in 0..1 with a 500ms decay. */
          hatSmoothed: number;
          /** Smoothed `any` curve in 0..1 with a 500ms decay. */
          anySmoothed: number;
          /** Single-frame boolean triggers OR-accumulated between frames, so no detected beat is lost. */
          triggers: {
              /** `true` for one frame when any beat is detected. */
              any: boolean;
              /** `true` for one frame when a kick is detected. */
              kick: boolean;
              /** `true` for one frame when a snare is detected. */
              snare: boolean;
              /** `true` for one frame when a hi-hat is detected. */
              hat: boolean;
          };
          /** All beats detected since the last render frame (zero or more). Cleared each frame. */
          events: Array<{
              /** Which drum was detected. */
              type: 'kick' | 'snare' | 'hat';
              /** Timestamp of the detection in milliseconds. */
              time: number;
              /** Strength of the beat in 0..1. */
              strength: number;
          }>;
          /** Currently detected tempo in beats per minute. Defaults to `120` when no audio. */
          bpm: number;
          /** Confidence of the BPM tracker in 0..1. */
          confidence: number;
          /** `true` when the BPM tracker has a stable lock on the current tempo. */
          isLocked: boolean;
      };
      /** High-level spectral features derived from the FFT. */
      spectral: {
          /** Normalized spectral centroid in 0..1. Higher values mean brighter, more treble-heavy sound. */
          brightness: number;
          /** Normalized spectral flatness in 0..1. Higher values mean noisier (white-noise-like) sound; lower values mean tonal. */
          flatness: number;
      };
      /**
       * Returns the raw FFT magnitude spectrum as a `Uint8Array` (1024 bins, each 0..255).
       * Bin `i` covers frequency `i × (sampleRate / fftSize)`. The returned array is a
       * snapshot from the latest analysis tick; repeated calls in the same frame return the same data.
       *
       * @example
       * const fft = viji.audio.getFrequencyData();
       * for (let i = 0; i < fft.length; i++) drawBar(i, fft[i] / 255);
       */
      getFrequencyData: () => Uint8Array;
      /**
       * Returns the raw time-domain waveform as a `Float32Array` (2048 PCM samples in -1..1).
       * The returned array is a snapshot; repeated calls in the same frame return the same data.
       *
       * @example
       * const wave = viji.audio.getWaveform();
       * for (let i = 0; i < wave.length; i++) drawSample(i, wave[i]);
       */
      getWaveform: () => Float32Array;
  }

  /**
   * Lightweight audio analysis for additional or device audio streams. A strict
   * subset of `AudioAPI` that exposes volume, frequency bands, spectral features,
   * and raw FFT/waveform access, but **no beat detection** (no `beat`, no BPM,
   * no triggers, no events).
   */
  interface AudioStreamAPI {
      /** `true` when this audio stream is connected; otherwise all numeric fields read as `0`. */
      isConnected: boolean;
      /** Overall loudness across the stream. */
      volume: {
          /** Instantaneous RMS volume in 0..1. */
          current: number;
          /** Instantaneous peak amplitude in 0..1. */
          peak: number;
          /** Volume with a 200ms decay envelope. */
          smoothed: number;
      };
      /** Energy in five fixed frequency bands, plus a smoothed companion (~150ms decay) for each. All in 0..1. */
      bands: {
          /** Instant low-band energy (20-120 Hz). 0..1. */
          low: number;
          /** Instant low-mid energy (120-400 Hz). 0..1. */
          lowMid: number;
          /** Instant mid energy (400-1600 Hz). 0..1. */
          mid: number;
          /** Instant high-mid energy (1600-6000 Hz). 0..1. */
          highMid: number;
          /** Instant high-band energy (6000-16000 Hz). 0..1. */
          high: number;
          /** Smoothed `low` (~150ms decay). 0..1. */
          lowSmoothed: number;
          /** Smoothed `lowMid` (~150ms decay). 0..1. */
          lowMidSmoothed: number;
          /** Smoothed `mid` (~150ms decay). 0..1. */
          midSmoothed: number;
          /** Smoothed `highMid` (~150ms decay). 0..1. */
          highMidSmoothed: number;
          /** Smoothed `high` (~150ms decay). 0..1. */
          highSmoothed: number;
      };
      /** High-level spectral features derived from the FFT. */
      spectral: {
          /** Normalized spectral centroid in 0..1. */
          brightness: number;
          /** Normalized spectral flatness in 0..1. */
          flatness: number;
      };
      /**
       * Returns the raw FFT magnitude spectrum for this stream as a `Uint8Array`
       * (1024 bins, each 0..255). Snapshot semantics; see `AudioAPI.getFrequencyData`.
       */
      getFrequencyData: () => Uint8Array;
      /**
       * Returns the raw time-domain waveform for this stream as a `Float32Array`
       * (2048 samples, each -1..1). Snapshot semantics; see `AudioAPI.getWaveform`.
       */
      getWaveform: () => Float32Array;
  }

  /**
   * Configuration object passed to `viji.button()`. The host renders a clickable
   * momentary button. The resulting `value` is `true` for one frame after press,
   * then auto-resets to `false`.
   */
  interface ButtonConfig {
      /** Display name shown on the button in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.button()`. `value` is a momentary flag:
   * `true` for exactly one frame after the user clicks, then auto-resets to
   * `false`. Use it to trigger discrete events from `render()`.
   */
  interface ButtonParameter {
      /** `true` for the single frame after the user clicks the button, `false` otherwise. */
      value: boolean;
      /** Display name shown on the button in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Options accepted by `VijiCore.captureFrame()` for snapshotting the current
   * scene. Controls output format, encoding, and target resolution / aspect.
   */
  interface CaptureFrameOptions {
      /** Output format: `'blob'` for an encoded image, `'bitmap'` for a GPU-friendly `ImageBitmap`. Default: `'blob'`. */
      format?: 'blob' | 'bitmap';
      /** MIME type for `'blob'` output (ignored for `'bitmap'`). Examples: `'image/png'`, `'image/jpeg'`, `'image/webp'`. */
      type?: string;
      /**
       * Target resolution.
       * - `number`: scale factor relative to the current canvas size (e.g., `0.5` = 50%).
       * - `Resolution`: exact output size. If the aspect ratio differs from the
       *   canvas, the source is center-cropped before scaling.
       */
      resolution?: number | Resolution;
  }

  /**
   * Configuration object passed to `viji.color()`. Defines the color picker's
   * label and UI grouping/visibility.
   */
  interface ColorConfig {
      /** Display name shown next to the color swatch in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Accepted input forms for color parameters. Internally normalized to canonical
   * lowercase 6-digit hex `#rrggbb`. See `viji.color()` for full documentation.
   *
   * - `string`: hex (`#rrggbb`, `#rgb`), CSS `rgb(r, g, b)`, CSS `hsl(h, s%, l%)`,
   *   GLSL-style `vec3(r, g, b)` (0..1), or P5-style `hsb(h, s, b)` (0..360 / 0..100 / 0..100)
   * - `{ r, g, b }`: RGB object with components in 0..255
   * - `{ h, s, b }`: HSB object with `h` in 0..360, `s` and `b` in 0..100
   */
  type ColorInput = string | {
      /** Red component in 0..255. */
      r: number;
      /** Green component in 0..255. */
      g: number;
      /** Blue component in 0..255. */
      b: number;
  } | {
      /** Hue in 0..360. */
      h: number;
      /** Saturation in 0..100. */
      s: number;
      /** Brightness in 0..100. */
      b: number;
  };

  /**
   * Reactive object returned by `viji.color()`. `value` is always a canonical
   * lowercase 6-digit hex string; `rgb` and `hsb` are derived accessors that are
   * recomputed (and frozen) every time the user changes the color.
   */
  interface ColorParameter {
      /** Canonical hex color string `#rrggbb` (lowercase). Updates in real-time. */
      value: string;
      /** RGB components as integers in 0..255 (frozen, recomputed when value changes). */
      rgb: {
          /** Red component, integer in 0..255. */
          r: number;
          /** Green component, integer in 0..255. */
          g: number;
          /** Blue component, integer in 0..255. */
          b: number;
      };
      /** HSB components: `h` in 0..360, `s` and `b` in 0..100 (frozen, recomputed when value changes). */
      hsb: {
          /** Hue in 0..360. */
          h: number;
          /** Saturation in 0..100. */
          s: number;
          /** Brightness in 0..100. */
          b: number;
      };
      /** Display name shown next to the swatch in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Configuration object passed to `viji.coordinate()`. The host renders a 2D
   * draggable pad; both `x` and `y` are clamped to `-1..1`.
   */
  interface CoordinateConfig {
      /** Snap increment for both `x` and `y` components. Default: `0.01`. */
      step?: number;
      /** Display name shown next to the pad in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.coordinate()`. `value.x` and `value.y` are
   * always in `-1..1`; the host UI renders a draggable 2D pad.
   */
  interface CoordinateParameter {
      /** Current `{ x, y }` position. Both components are in `-1..1`. Updates in real-time. */
      value: CoordinateValue;
      /** Snap increment for both `x` and `y` components. */
      step: number;
      /** Display name shown next to the pad in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Two-component coordinate, both axes in `-1..1` (centered on the canvas).
   * Returned and consumed by `viji.coordinate()`.
   */
  type CoordinateValue = {
      /** Horizontal axis in `-1..1` (left to right, `0` = canvas center). */
      x: number;
      /** Vertical axis in `-1..1` (bottom to top, `0` = canvas center). */
      y: number;
  };

  /**
   * Identifier for an individual computer-vision feature pipeline. Returned by
   * `VideoAPI.cv.getActiveFeatures()` and used internally to track state.
   */
  type CVFeature = 'faceDetection' | 'faceMesh' | 'handTracking' | 'poseDetection' | 'bodySegmentation' | 'emotionDetection';

  /**
   * Computer-vision processing rate relative to the scene's render rate.
   * `'full'` runs CV every frame, `'half'` every other frame, `'quarter'` every
   * fourth, `'eighth'` every eighth. Lower rates reduce CPU/GPU cost.
   */
  type CVFrameRateMode = 'full' | 'half' | 'quarter' | 'eighth';

  /**
   * Device motion data sourced from the platform's `DeviceMotionEvent`. All
   * accelerations are in metres per second squared (m/s²); rotation rates are
   * in degrees per second. Inner objects may be `null` when the underlying
   * sensor is unavailable, and individual axes may be `null` on platforms that
   * report only some components.
   */
  interface DeviceMotionData {
      /** Acceleration without gravity (m/s²). `null` when no accelerometer is available. */
      acceleration: {
          /** Acceleration along the device's X axis in m/s². `null` if unsupported. */
          x: number | null;
          /** Acceleration along the device's Y axis in m/s². `null` if unsupported. */
          y: number | null;
          /** Acceleration along the device's Z axis in m/s². `null` if unsupported. */
          z: number | null;
      } | null;
      /** Acceleration including gravity (m/s²). `null` when no accelerometer is available. */
      accelerationIncludingGravity: {
          /** Acceleration including gravity along the device's X axis in m/s². `null` if unsupported. */
          x: number | null;
          /** Acceleration including gravity along the device's Y axis in m/s². `null` if unsupported. */
          y: number | null;
          /** Acceleration including gravity along the device's Z axis in m/s². `null` if unsupported. */
          z: number | null;
      } | null;
      /** Angular rotation rate (degrees per second). `null` when no gyroscope is available. */
      rotationRate: {
          /** Rotation rate around the device's Z axis in deg/s. `null` if unsupported. */
          alpha: number | null;
          /** Rotation rate around the device's X axis in deg/s. `null` if unsupported. */
          beta: number | null;
          /** Rotation rate around the device's Y axis in deg/s. `null` if unsupported. */
          gamma: number | null;
      } | null;
      /** Interval between sensor updates in milliseconds. */
      interval: number;
  }

  /**
   * DeviceOrientationEvent data
   * Matches native DeviceOrientationEvent structure
   */
  interface DeviceOrientationData {
      /** Rotation around Z-axis (0-360 degrees, compass heading) */
      alpha: number | null;
      /** Rotation around X-axis (-180 to 180 degrees, front-to-back tilt) */
      beta: number | null;
      /** Rotation around Y-axis (-90 to 90 degrees, left-to-right tilt) */
      gamma: number | null;
      /** True if using magnetometer (compass) for absolute orientation */
      absolute: boolean;
  }

  /**
   * Sensor snapshot for the device running the scene. Both fields read `null`
   * when the corresponding sensor is unavailable or has not delivered a sample yet.
   */
  interface DeviceSensorState {
      /** Motion data (acceleration, rotation rate, sampling interval). `null` if no motion sensor is available. */
      motion: DeviceMotionData | null;
      /** Orientation data (alpha/beta/gamma in degrees). `null` if no orientation sensor is available. */
      orientation: DeviceOrientationData | null;
  }

  /**
   * External device state (includes id and name)
   */
  interface DeviceState extends DeviceSensorState {
      /** Unique device identifier */
      id: string;
      /** User-friendly device name */
      name: string;
      /** Device camera video (null if not available) */
      video: VideoAPI | null;
      /** Device audio stream (null if not available) */
      audio: AudioStreamAPI | null;
  }

  /**
   * 52 ARKit-compatible face blendshape coefficients (each in 0..1) derived from
   * MediaPipe FaceLandmarker. All fields are `0` unless emotion detection is
   * enabled. Available on each `FaceData.blendshapes`.
   */
  interface FaceBlendshapes {
      /** Coefficient (0..1) for the inner brow of the left eye pulling down. */
      browDownLeft: number;
      /** Coefficient (0..1) for the inner brow of the right eye pulling down. */
      browDownRight: number;
      /** Coefficient (0..1) for both inner brows pulling up. */
      browInnerUp: number;
      /** Coefficient (0..1) for the outer left brow pulling up. */
      browOuterUpLeft: number;
      /** Coefficient (0..1) for the outer right brow pulling up. */
      browOuterUpRight: number;
      /** Coefficient (0..1) for puffed cheeks. */
      cheekPuff: number;
      /** Coefficient (0..1) for the left cheek squinting. */
      cheekSquintLeft: number;
      /** Coefficient (0..1) for the right cheek squinting. */
      cheekSquintRight: number;
      /** Coefficient (0..1) for the left eyelid closing. */
      eyeBlinkLeft: number;
      /** Coefficient (0..1) for the right eyelid closing. */
      eyeBlinkRight: number;
      /** Coefficient (0..1) for the left eye looking down. */
      eyeLookDownLeft: number;
      /** Coefficient (0..1) for the right eye looking down. */
      eyeLookDownRight: number;
      /** Coefficient (0..1) for the left eye looking inward (toward the nose). */
      eyeLookInLeft: number;
      /** Coefficient (0..1) for the right eye looking inward. */
      eyeLookInRight: number;
      /** Coefficient (0..1) for the left eye looking outward (away from the nose). */
      eyeLookOutLeft: number;
      /** Coefficient (0..1) for the right eye looking outward. */
      eyeLookOutRight: number;
      /** Coefficient (0..1) for the left eye looking up. */
      eyeLookUpLeft: number;
      /** Coefficient (0..1) for the right eye looking up. */
      eyeLookUpRight: number;
      /** Coefficient (0..1) for the left eye squinting. */
      eyeSquintLeft: number;
      /** Coefficient (0..1) for the right eye squinting. */
      eyeSquintRight: number;
      /** Coefficient (0..1) for the left eye opening wide. */
      eyeWideLeft: number;
      /** Coefficient (0..1) for the right eye opening wide. */
      eyeWideRight: number;
      /** Coefficient (0..1) for the jaw moving forward. */
      jawForward: number;
      /** Coefficient (0..1) for the jaw moving left. */
      jawLeft: number;
      /** Coefficient (0..1) for the jaw opening (mouth open). */
      jawOpen: number;
      /** Coefficient (0..1) for the jaw moving right. */
      jawRight: number;
      /** Coefficient (0..1) for the mouth closing tightly. */
      mouthClose: number;
      /** Coefficient (0..1) for a dimple appearing on the left side. */
      mouthDimpleLeft: number;
      /** Coefficient (0..1) for a dimple appearing on the right side. */
      mouthDimpleRight: number;
      /** Coefficient (0..1) for the left mouth corner pulling down (frown). */
      mouthFrownLeft: number;
      /** Coefficient (0..1) for the right mouth corner pulling down (frown). */
      mouthFrownRight: number;
      /** Coefficient (0..1) for funneling the lips outward. */
      mouthFunnel: number;
      /** Coefficient (0..1) for the mouth shifting left. */
      mouthLeft: number;
      /** Coefficient (0..1) for the lower lip on the left pulling down. */
      mouthLowerDownLeft: number;
      /** Coefficient (0..1) for the lower lip on the right pulling down. */
      mouthLowerDownRight: number;
      /** Coefficient (0..1) for the left lip pressing inward. */
      mouthPressLeft: number;
      /** Coefficient (0..1) for the right lip pressing inward. */
      mouthPressRight: number;
      /** Coefficient (0..1) for puckered lips. */
      mouthPucker: number;
      /** Coefficient (0..1) for the mouth shifting right. */
      mouthRight: number;
      /** Coefficient (0..1) for the lower lip rolling inward. */
      mouthRollLower: number;
      /** Coefficient (0..1) for the upper lip rolling inward. */
      mouthRollUpper: number;
      /** Coefficient (0..1) for the lower lip shrugging. */
      mouthShrugLower: number;
      /** Coefficient (0..1) for the upper lip shrugging. */
      mouthShrugUpper: number;
      /** Coefficient (0..1) for the left mouth corner pulling up (smile). */
      mouthSmileLeft: number;
      /** Coefficient (0..1) for the right mouth corner pulling up (smile). */
      mouthSmileRight: number;
      /** Coefficient (0..1) for the left mouth corner stretching outward. */
      mouthStretchLeft: number;
      /** Coefficient (0..1) for the right mouth corner stretching outward. */
      mouthStretchRight: number;
      /** Coefficient (0..1) for the upper lip on the left raising. */
      mouthUpperUpLeft: number;
      /** Coefficient (0..1) for the upper lip on the right raising. */
      mouthUpperUpRight: number;
      /** Coefficient (0..1) for the left side of the nose sneering. */
      noseSneerLeft: number;
      /** Coefficient (0..1) for the right side of the nose sneering. */
      noseSneerRight: number;
      /** Coefficient (0..1) for the tongue protruding. */
      tongueOut: number;
  }

  /**
   * One detected face produced by the video CV pipeline. Each field is
   * populated only by its source model; fields whose source model is not
   * enabled read as `null` so the absence is loud rather than silent. Enable
   * the relevant `enable*` verb on `viji.video.cv` to populate each group:
   *
   * - `bounds`, `center`, `confidence`: populated by `enableFaceDetection(true)`.
   * - `landmarks`: populated by `enableFaceMesh(true)` (empty array otherwise).
   * - `headPose`: populated by `enableFaceMesh(true)`.
   * - `blendshapes`, `expressions`: populated by `enableEmotionDetection(true)`.
   *
   * If a field is `null`, call the corresponding `enable*` verb on
   * `viji.video.cv` to populate it. Each active CV feature consumes its own
   * WebGL context for MediaPipe; enabling many at once on lower-end hardware
   * can hit context limits.
   */
  interface FaceData {
      /** Index-based face identifier (`0`, `1`, …). Stable within a single frame, may change between frames. */
      id: number;
      /** Bounding box, all components normalized to 0..1 of the source frame. `null` unless face detection is enabled. */
      bounds: {
          /** Left edge of the face bounding box, normalized 0..1. */
          x: number;
          /** Top edge of the face bounding box, normalized 0..1. */
          y: number;
          /** Width of the face bounding box, normalized 0..1. */
          width: number;
          /** Height of the face bounding box, normalized 0..1. */
          height: number;
      } | null;
      /** Center of the bounding box, normalized 0..1. `null` unless face detection is enabled. */
      center: {
          /** Horizontal center of the face, normalized 0..1. */
          x: number;
          /** Vertical center of the face, normalized 0..1. */
          y: number;
      } | null;
      /** Detection confidence in 0..1. `null` unless face detection is enabled. */
      confidence: number | null;
      /** 468 normalized face mesh landmarks when face mesh is enabled; empty array otherwise. */
      landmarks: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
          /** Optional depth (relative). */
          z?: number;
      }[];
      /** Estimated head rotation in degrees. `null` unless face mesh is enabled. */
      headPose: {
          /** Up/down rotation in degrees (-90..90). Positive = head looking up, negative = looking down. */
          pitch: number;
          /** Left/right rotation in degrees (-90..90). Positive = head turned to the subject's right, negative = to the left. */
          yaw: number;
          /** Tilt rotation in degrees (-180..180). Positive = head tilted to the subject's right (right ear toward right shoulder). */
          roll: number;
      } | null;
      /** Seven expression confidence scores in 0..1. `null` unless emotion detection is enabled. */
      expressions: {
          /** Confidence (0..1) of a neutral expression. */
          neutral: number;
          /** Confidence (0..1) of a happy / smiling expression. */
          happy: number;
          /** Confidence (0..1) of a sad expression. */
          sad: number;
          /** Confidence (0..1) of an angry expression. */
          angry: number;
          /** Confidence (0..1) of a surprised expression. */
          surprised: number;
          /** Confidence (0..1) of a disgusted expression. */
          disgusted: number;
          /** Confidence (0..1) of a fearful expression. */
          fearful: number;
      } | null;
      /** 52 ARKit-compatible facial muscle coefficients. `null` unless emotion detection is enabled. */
      blendshapes: FaceBlendshapes | null;
  }

  /**
   * Render-loop pacing mode. `'full'` runs `render()` on every animation frame
   * (~60 FPS). `'half'` runs every second frame (~30 FPS), saving CPU/GPU on
   * lower-cost scenes.
   */
  type FrameRateMode = 'full' | 'half';

  /**
   * One named frequency band reported by the audio analyzer
   * (e.g., `{ name: 'low', min: 20, max: 120 }`). Frequencies are in Hertz.
   */
  interface FrequencyBand {
      /** Human-readable band name (e.g., `'low'`, `'mid'`, `'high'`). */
      name: string;
      /** Lower edge of the band in Hertz (inclusive). */
      min: number;
      /** Upper edge of the band in Hertz (exclusive). */
      max: number;
  }

  /**
   * One detected hand produced by the video CV pipeline. Carries 21 landmarks,
   * a palm shortcut, and ML-classified gesture confidence scores. Up to two
   * hands appear in `VideoAPI.hands` simultaneously.
   */
  interface HandData {
      /** Index-based hand identifier (`0` or `1`). */
      id: number;
      /** Which hand. Always lowercase. */
      handedness: 'left' | 'right';
      /** Detection confidence in 0..1. */
      confidence: number;
      /** Bounding box, all components normalized to 0..1 of the source frame. */
      bounds: {
          /** Left edge of the hand bounding box, normalized 0..1. */
          x: number;
          /** Top edge of the hand bounding box, normalized 0..1. */
          y: number;
          /** Width of the hand bounding box, normalized 0..1. */
          width: number;
          /** Height of the hand bounding box, normalized 0..1. */
          height: number;
      };
      /** 21 MediaPipe hand landmarks, normalized 0..1. */
      landmarks: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
          /** Depth (relative). */
          z: number;
      }[];
      /** Palm center, equivalent to `landmarks[9]` (middle-finger MCP). */
      palm: {
          /** Palm horizontal coordinate, normalized 0..1. */
          x: number;
          /** Palm vertical coordinate, normalized 0..1. */
          y: number;
          /** Palm depth (relative). */
          z: number;
      };
      /**
       * ML-classified gesture confidences from MediaPipe GestureRecognizer.
       * Each value is in 0..1; multiple gestures can fire simultaneously.
       */
      gestures: {
          /** Confidence (0..1) of a closed-fist gesture. */
          fist: number;
          /** Confidence (0..1) of an open-palm gesture. */
          openPalm: number;
          /** Confidence (0..1) of a peace / victory sign. */
          peace: number;
          /** Confidence (0..1) of a thumbs-up gesture. */
          thumbsUp: number;
          /** Confidence (0..1) of a thumbs-down gesture. */
          thumbsDown: number;
          /** Confidence (0..1) of a pointing-up gesture. */
          pointing: number;
          /** Confidence (0..1) of the ASL "I love you" sign. */
          iLoveYou: number;
      };
  }

  /**
   * Configuration object passed to `viji.image()`. The host renders an upload
   * field; the user-supplied image becomes available as an `ImageBitmap` (Native)
   * or a P5 image wrapper (P5 renderer).
   */
  interface ImageConfig {
      /** Display name shown next to the upload field in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.image()`. `value` is `null` until the user
   * uploads an image; in P5 scenes a `.p5` accessor is also added at runtime so
   * the image works directly with `image()`, `texture()`, etc.
   */
  interface ImageParameter {
      /** Uploaded image as an `ImageBitmap`, or `null` until the user provides one. */
      value: ImageBitmap | null;
      /** P5-compatible image wrapper. Only available in the P5 renderer; added at runtime by the P5 adapter. */
      readonly p5?: any;
      /** Display name shown next to the upload field in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Keyboard input state for the current frame: per-key press / hold / release
   * queries (case-insensitive), the live set of held keys, modifier flags, and
   * a Shadertoy-compatible texture buffer for shader-side keyboard reads.
   *
   * Key names follow the browser's `event.key` standard
   * (`'a'`, `'arrowup'`, `' '` for Space, `'enter'`, `'escape'`, etc.).
   */
  interface KeyboardAPI {
      /** Returns `true` while the named key is held. Case-insensitive. */
      isPressed(key: string): boolean;
      /** Returns `true` for exactly one frame on the rising edge of the named key (no key-repeat). Case-insensitive. */
      wasPressed(key: string): boolean;
      /** Returns `true` for exactly one frame on the falling edge of the named key. Case-insensitive. */
      wasReleased(key: string): boolean;
      /** Set of all currently held keys, lowercased. Persists across frames. */
      activeKeys: Set<string>;
      /** Set of keys whose key-down fired this frame, lowercased. Cleared each frame. */
      pressedThisFrame: Set<string>;
      /** Set of keys whose key-up fired this frame, lowercased. Cleared each frame. */
      releasedThisFrame: Set<string>;
      /** Most recently pressed key in its original case (e.g., `'A'` when Shift is held). */
      lastKeyPressed: string;
      /** Most recently released key in its original case. */
      lastKeyReleased: string;
      /** `true` while the Shift key is held. */
      shift: boolean;
      /** `true` while the Ctrl key is held. */
      ctrl: boolean;
      /** `true` while the Alt (or Option) key is held. */
      alt: boolean;
      /** `true` while the Meta (Windows / Cmd) key is held. */
      meta: boolean;
      /**
       * Shadertoy-compatible 256×3 keyboard texture (256 keycodes × 3 rows).
       * Row 0: held state (1 / 0). Row 1: pressed-this-frame (1 / 0). Row 2: per-key toggle.
       * Bound to a uniform sampler in shader scenes; rarely needed in JS.
       */
      textureData: Uint8Array;
  }

  /**
   * Mouse input state for the current frame: position, individual button states,
   * per-frame movement, scroll wheel, and frame-based press / release events.
   * Coordinates are in canvas-space pixels with `(0, 0)` at the top-left corner.
   *
   * For interactions that should also work on touch devices, prefer `viji.pointer`.
   */
  interface MouseAPI {
      /** Canvas-space X position in pixels. */
      x: number;
      /** Canvas-space Y position in pixels. */
      y: number;
      /** `true` when the cursor is currently over the canvas. */
      isInCanvas: boolean;
      /** `true` if any mouse button is currently held. */
      isPressed: boolean;
      /** `true` while the left mouse button is held. */
      leftButton: boolean;
      /** `true` while the right mouse button is held. The browser context menu is suppressed on the canvas. */
      rightButton: boolean;
      /** `true` while the middle (wheel) mouse button is held. */
      middleButton: boolean;
      /** Horizontal movement this frame in pixels. Resets to `0` each frame. */
      deltaX: number;
      /** Vertical movement this frame in pixels. Resets to `0` each frame. */
      deltaY: number;
      /** Vertical scroll accumulated this frame (alias of `wheelY`). Resets to `0` each frame. */
      wheelDelta: number;
      /** Horizontal scroll accumulated this frame (e.g., trackpad gestures, tilt-wheel mice). Resets to `0` each frame. */
      wheelX: number;
      /** Vertical scroll accumulated this frame. Resets to `0` each frame. */
      wheelY: number;
      /** `true` for exactly one frame when any button is first pressed, then auto-resets. */
      wasPressed: boolean;
      /** `true` for exactly one frame when any button is released, then auto-resets. */
      wasReleased: boolean;
      /** `true` if the mouse moved this frame (any non-zero `deltaX` / `deltaY`). Resets each frame. */
      wasMoved: boolean;
  }

  /**
   * Configuration object passed to `viji.number()`. Defines the bounds, step,
   * label, and UI grouping/visibility for a precise numeric input.
   */
  interface NumberConfig {
      /** Inclusive minimum value. Default: `0`. */
      min?: number;
      /** Inclusive maximum value. Default: `100`. */
      max?: number;
      /**
       * Increment between values. When omitted, auto-derives from `min`/`max` as
       * the largest power of 10 giving ~100 positions across the range (e.g.,
       * range `0..1` → `0.01`, range `0..100` → `1`, range `0.1..5` → `0.01`).
       * Specify explicitly for integer counts (`step: 1`) or fine precision
       * (`step: 0.001`).
       */
      step?: number;
      /** Display name shown next to the input in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.number()`. Like `SliderParameter` but the
   * host UI renders a precise numeric input rather than a draggable slider.
   */
  interface NumberParameter {
      /** Current numeric value in the configured `min..max` range. Updates in real-time. */
      value: number;
      /** Inclusive minimum value. */
      min: number;
      /** Inclusive maximum value. */
      max: number;
      /** Increment between values. */
      step: number;
      /** Display name shown next to the input in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Visibility category for a parameter. The host hides parameters whose category
   * is not currently available (e.g., `'audio'` controls hide when no audio
   * source is connected). `'general'` parameters are always visible.
   */
  type ParameterCategory = 'audio' | 'video' | 'interaction' | 'general';

  /**
   * Unified pointer that abstracts mouse and primary touch into a single input
   * stream. Recommended for click / drag / position-tracking interactions that
   * should work identically on desktop and mobile.
   *
   * Use `viji.mouse` for mouse-specific features (right click, middle click,
   * scroll wheel); use `viji.touches` for multi-touch, pressure, or radius.
   */
  interface PointerAPI {
      /** Canvas-space X position in pixels. */
      x: number;
      /** Canvas-space Y position in pixels. */
      y: number;
      /** Horizontal movement since the last frame in pixels. */
      deltaX: number;
      /** Vertical movement since the last frame in pixels. */
      deltaY: number;
      /** `true` while the pointer is "down": left mouse button held, or a touch active. */
      isDown: boolean;
      /** `true` for exactly one frame when the pointer becomes down, then auto-resets. */
      wasPressed: boolean;
      /** `true` for exactly one frame when the pointer is released, then auto-resets. */
      wasReleased: boolean;
      /** `true` while the pointer is within the canvas bounds. */
      isInCanvas: boolean;
      /** Currently active input source: `'touch'` when at least one touch is active, otherwise `'mouse'` (or `'none'` when the cursor is off-canvas). */
      type: 'mouse' | 'touch' | 'none';
  }

  /**
   * Detected body pose from MediaPipe BlazePose. `landmarks` contains 33 points;
   * the `face` / `torso` / `leftArm` / `rightArm` / `leftLeg` / `rightLeg` arrays
   * are pre-grouped slices for convenience.
   */
  interface PoseData {
      /** Average landmark visibility in 0..1. */
      confidence: number;
      /** 33 BlazePose landmarks, normalized 0..1. Each carries a per-point `visibility` score. */
      landmarks: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
          /** Depth (relative). */
          z: number;
          /** Visibility / confidence of this individual landmark in 0..1. */
          visibility: number;
      }[];
      /** Face landmarks (BlazePose indices 0..10). Coordinates normalized 0..1. */
      face: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
      }[];
      /** Torso landmarks (BlazePose indices 11, 12, 23, 24). Coordinates normalized 0..1. */
      torso: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
      }[];
      /** Left-arm landmarks (BlazePose indices 11, 13, 15). Coordinates normalized 0..1. */
      leftArm: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
      }[];
      /** Right-arm landmarks (BlazePose indices 12, 14, 16). Coordinates normalized 0..1. */
      rightArm: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
      }[];
      /** Left-leg landmarks (BlazePose indices 23, 25, 27, 29, 31). Coordinates normalized 0..1. */
      leftLeg: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
      }[];
      /** Right-leg landmarks (BlazePose indices 24, 26, 28, 30, 32). Coordinates normalized 0..1. */
      rightLeg: {
          /** Horizontal landmark coordinate, normalized 0..1. */
          x: number;
          /** Vertical landmark coordinate, normalized 0..1. */
          y: number;
      }[];
  }

  /**
   * Discrete pixel dimensions for a canvas, frame capture, or render target.
   */
  type Resolution = {
      /** Width in pixels (positive integer). */
      width: number;
      /** Height in pixels (positive integer). */
      height: number;
  };

  /**
   * Per-pixel person/background segmentation mask. Mask dimensions reflect the
   * ML model's output and may differ from the source video frame.
   */
  interface SegmentationData {
      /** Flat per-pixel mask: each byte is `0` (background) or `1` (person). Length = `width * height`. */
      mask: Uint8Array;
      /** Mask width in pixels. */
      width: number;
      /** Mask height in pixels. */
      height: number;
  }

  /**
   * Configuration object passed to `viji.select()`. The host renders the choices
   * as a dropdown or segmented control.
   */
  interface SelectConfig {
      /** Available choices. The element type (`string` or `number`) becomes the parameter's value type. Required. */
      options: string[] | number[];
      /** Display name shown next to the dropdown in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.select()`. `value` matches the element type
   * of the configured `options` array (`string` or `number`).
   */
  interface SelectParameter {
      /** Currently selected option. Updates in real-time when the user changes the selection. */
      value: string | number;
      /** The full list of choices originally configured. */
      options: string[] | number[];
      /** Display name shown next to the dropdown in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Configuration object passed to `viji.slider()`. Defines the slider's bounds,
   * increment, label, and UI grouping/visibility.
   */
  interface SliderConfig {
      /** Inclusive minimum value of the slider. Default: `0`. */
      min?: number;
      /** Inclusive maximum value of the slider. Default: `100`. */
      max?: number;
      /**
       * Increment between values. When omitted, auto-derives from `min`/`max` as
       * the largest power of 10 giving ~100 positions across the range (e.g.,
       * range `0..1` → `0.01`, range `0..100` → `1`, range `0.1..5` → `0.01`,
       * range `0..1000` → `10`). Specify explicitly for integer counts
       * (`step: 1` on a range of `3..30`) or fine precision (`step: 0.001`).
       */
      step?: number;
      /** Display name shown next to the control in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.slider()`. `value` updates in real-time as
   * the user drags the control; the remaining fields mirror the configuration.
   */
  interface SliderParameter {
      /** Current slider value in the configured `min..max` range. Updates in real-time. */
      value: number;
      /** Inclusive minimum value of the slider. */
      min: number;
      /** Inclusive maximum value of the slider. */
      max: number;
      /** Increment between values. */
      step: number;
      /** Display name shown next to the control in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Configuration object passed to `viji.text()`. The host renders a single-line
   * text input field.
   */
  interface TextConfig {
      /** Display name shown next to the text field in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
      /** Maximum character count enforced by the host UI. Default: `1000`. */
      maxLength?: number;
  }

  /**
   * Reactive object returned by `viji.text()`. `value` updates as the user types
   * and is enforced to be no longer than `maxLength` by the host UI.
   */
  interface TextParameter {
      /** Current text content. Updates in real-time as the user types. */
      value: string;
      /** Maximum character count enforced by the host UI. */
      maxLength?: number;
      /** Display name shown next to the input in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Configuration object passed to `viji.toggle()`. Defines the on/off switch's
   * label and UI grouping/visibility.
   */
  interface ToggleConfig {
      /** Display name shown next to the switch in the parameter UI. Required. */
      label: string;
      /** Optional tooltip / help text shown next to the control. */
      description?: string;
      /** Group name for organizing related parameters under a shared heading. Default: `'general'`. */
      group?: string;
      /** Visibility category. The control hides when its capability is unavailable. Default: `'general'`. */
      category?: ParameterCategory;
  }

  /**
   * Reactive object returned by `viji.toggle()`. `value` flips between `true`
   * and `false` as the user interacts with the switch.
   */
  interface ToggleParameter {
      /** Current state of the switch. Updates in real-time when the user toggles it. */
      value: boolean;
      /** Display name shown next to the switch in the parameter UI. */
      label: string;
      /** Tooltip / help text for the control (empty string when not configured). */
      description?: string;
      /** Group name under which the control appears in the UI. */
      group: string;
      /** Visibility category that controls when the parameter is shown. */
      category: ParameterCategory;
  }

  /**
   * Multi-touch input state. Lists all active touches, plus per-frame
   * `started` / `moved` / `ended` arrays for frame-based gesture handling.
   *
   * For single-point interactions that should also work with a mouse, prefer
   * `viji.pointer`. It switches automatically between mouse and primary touch.
   */
  interface TouchAPI {
      /** All currently active touch points. Order is stable but not ordered by id. */
      points: TouchPoint[];
      /** Number of currently active touches (equivalent to `points.length`). */
      count: number;
      /** Touches that started this frame (also visible in `points`). Cleared each frame. */
      started: TouchPoint[];
      /** Touches that moved this frame. Cleared each frame. */
      moved: TouchPoint[];
      /** Touches that ended this frame. Each entry has `isEnding === true`. Cleared each frame. */
      ended: TouchPoint[];
      /** Convenience shortcut to `points[0]`, or `null` when no touches are active. */
      primary: TouchPoint | null;
  }

  /**
   * One active touch contact reported by `TouchAPI`. Position, pressure, radius,
   * rotation, per-frame movement, velocity, and lifecycle flags. Coordinates are
   * in canvas-space pixels.
   *
   * Some fields (notably `pressure` / `force`, `radiusX` / `radiusY`,
   * `rotationAngle`) are passed through from the device; values vary by
   * platform; many devices report `0` when unsupported.
   */
  interface TouchPoint {
      /** Stable touch identifier across frames for the duration of this contact. */
      id: number;
      /** Canvas-space X position in pixels. */
      x: number;
      /** Canvas-space Y position in pixels. */
      y: number;
      /** Touch pressure in 0..1 (device-dependent; often `0` on devices without force support). */
      pressure: number;
      /** Contact radius, `Math.max(radiusX, radiusY)` in pixels. */
      radius: number;
      /** Horizontal contact radius in pixels (raw device value). */
      radiusX: number;
      /** Vertical contact radius in pixels (raw device value). */
      radiusY: number;
      /** Contact area rotation in radians (raw device value). */
      rotationAngle: number;
      /** Touch force in 0..1; alias of `pressure`. */
      force: number;
      /** `true` while the touch position is within the canvas bounds. */
      isInCanvas: boolean;
      /** Horizontal movement since the last frame in pixels. */
      deltaX: number;
      /** Vertical movement since the last frame in pixels. */
      deltaY: number;
      /** Movement velocity in pixels per second, computed by Viji. */
      velocity: {
          /** Horizontal velocity component in pixels per second. */
          x: number;
          /** Vertical velocity component in pixels per second. */
          y: number;
      };
      /** `true` for exactly one frame when this touch starts, then auto-resets. */
      isNew: boolean;
      /** `true` while the touch is ongoing. */
      isActive: boolean;
      /** `true` for exactly one frame when this touch ends, then auto-resets. */
      isEnding: boolean;
  }

  const VERSION = "0.8.0";

  /**
   * Real-time video API: drawable frame, dimensions, and the source-side
   * properties of the connected stream. All computer-vision data and controls
   * (faces, hands, pose, segmentation, the analysed frame, and feature
   * enable/disable verbs) live on `viji.video.cv`.
   *
   * When `isConnected` is `false`, `currentFrame` is `null`, all dimensions are
   * `0`, and every member of `cv` holds its empty default.
   */
  interface VideoAPI {
      /** `true` when a video stream is connected. When `false`, all other fields hold their default empty values. */
      isConnected: boolean;
      /** Latest video frame, drawable directly with `ctx.drawImage()`. `null` when disconnected. */
      currentFrame: OffscreenCanvas | ImageBitmap | null;
      /** Source video frame width in pixels. `0` when disconnected. */
      frameWidth: number;
      /** Source video frame height in pixels. `0` when disconnected. */
      frameHeight: number;
      /** Source video frame rate in Hz (frames per second). `0` when disconnected. */
      frameRate: number;
      /**
       * Returns the latest frame as raw RGBA `ImageData` for per-pixel CPU analysis,
       * or `null` when disconnected. Slow compared to drawing `currentFrame` directly;
       * use only when you need to read individual pixel values.
       */
      getFrameData: () => ImageData | null;
      /**
       * Computer-vision surface for this video stream. Holds both the data
       * outputs (`faces`, `hands`, `pose`, `segmentation`, `analysedFrame`,
       * `getAnalysedFrameData`) and the verbs that activate them
       * (`enable*`/`disable*`/`getActiveFeatures`/`isProcessing`).
       *
       * Multiple features can be active simultaneously, but each adds CPU/GPU
       * and WebGL-context cost; toggle only what you need.
       *
       * `analysedFrame` and the result fields refresh together once per host
       * frame as an atomic snapshot: within a single `render()` call they are
       * always paired.
       */
      cv: VideoCVAPI;
  }

  /**
   * Computer-vision surface attached to a `VideoAPI`. Combines paired CV data
   * outputs (the analysed frame and its detection / segmentation results) with
   * the verbs that activate the underlying MediaPipe pipelines.
   *
   * Available only on the main video stream (`viji.video.cv`). Additional
   * streams (`viji.videoStreams[i].cv`) and device videos
   * (`viji.devices[i].video.cv`) do not run CV; their `cv` surface exists for
   * API consistency but the data fields stay at their empty defaults and the
   * `enable*` verbs are no-ops.
   */
  interface VideoCVAPI {
      /**
       * The exact video frame that produced the current `faces` / `hands` /
       * `pose` / `segmentation` results: the frame MediaPipe actually analysed.
       *
       * Choose between `analysedFrame` and `viji.video.currentFrame` by asking
       * whether the effect reads pixels from the displayed frame at CV-derived
       * positions. Reach for `analysedFrame` when the answer is yes
       * (compositing the segmentation mask onto the body, sampling skin tone
       * under a face landmark, warping the face along its mesh, texture-mapped
       * face filters): pairing the displayed frame with the CV results is
       * required for those effects. Stay on `currentFrame` when the answer is
       * no (drawing landmark dots, particles, debug overlays, geometry driven
       * by CV positions): `analysedFrame` only refreshes when MediaPipe
       * completes an inference, which is not every frame, so reaching for it
       * without a reason makes the displayed video stutter or hold between
       * inferences.
       *
       * Pairing guarantee: within a single `render()` call, `analysedFrame`
       * is paired with the values of `faces`, `hands`, `pose`, `segmentation`
       * read in the same call.
       *
       * `null` until the first CV inference completes after a feature is
       * enabled, after the video disconnects, or after a CV-feature toggle
       * clears state. A common pattern is
       * `viji.video.cv.analysedFrame ?? viji.video.currentFrame` to fall back
       * to the live feed during the brief startup window; expect a small
       * visual hitch when the source switches as the first inference lands.
       *
       * Read-only: this `OffscreenCanvas` is owned by the engine. Do not
       * mutate it (no `getContext`-and-paint, no `transferToImageBitmap`).
       * Drawing it as a source (`ctx.drawImage`, `gl.texImage2D`,
       * `p5.image(...)`) is the only supported usage.
       */
      analysedFrame: OffscreenCanvas | null;
      /**
       * Returns `analysedFrame` as raw RGBA `ImageData` for per-pixel CPU
       * analysis, or `null` when no CV result has landed yet (or the stream is
       * disconnected). Cached: re-extracted only when a new CV result arrives,
       * so multiple readers within a render share one allocation. Slow
       * compared to drawing `analysedFrame` directly. Use only when you need
       * pixel values aligned with CV landmark positions (e.g. sampling skin
       * colour at a face landmark).
       */
      getAnalysedFrameData: () => ImageData | null;
      /** Detected faces (empty array when face detection is off or no faces are visible). */
      faces: FaceData[];
      /** Detected hands (up to 2; empty array when hand tracking is off or no hands are visible). */
      hands: HandData[];
      /** Detected body pose, or `null` when pose detection is off or no pose is visible. */
      pose: PoseData | null;
      /** Body segmentation mask, or `null` when segmentation is off. */
      segmentation: SegmentationData | null;
      /**
       * Toggle face detection. Populates `face.bounds`, `face.center`,
       * `face.confidence` on each detected face. Independent of face mesh and
       * emotion detection.
       *
       * Safe to call from module scope as well as from inside `render()`. The
       * verb is idempotent and reference-counted; per-frame calls inside
       * `render()` (the canonical toggle-gated pattern) are cheap.
       *
       * @returns Resolves once the underlying pipeline has finished switching.
       */
      enableFaceDetection(enabled: boolean): Promise<void>;
      /**
       * Toggle 468-point face mesh and computed head pose (`pitch`, `yaw`,
       * `roll`). Populates `face.landmarks` and `face.headPose` on the face
       * entry. Does NOT populate `face.bounds` / `face.center` / `face.confidence`
       * — those are produced only by face detection. Enable both if you need
       * both, or compute bounds from `face.landmarks` min/max yourself.
       *
       * Safe to call from module scope as well as from inside `render()`. The
       * verb is idempotent and reference-counted.
       *
       * @returns Resolves once the pipeline has finished switching.
       */
      enableFaceMesh(enabled: boolean): Promise<void>;
      /**
       * Toggle 7 expressions + 52 ARKit blendshape coefficients on each face.
       * Populates `face.blendshapes` and `face.expressions`. Internally requires
       * the face landmarker model (loads it alongside `enableFaceMesh` if both
       * are enabled; loads it on its own otherwise so `face.landmarks` and
       * `face.headPose` are also populated). Does NOT populate
       * `face.bounds` / `face.center` / `face.confidence` — enable face
       * detection for those.
       *
       * Safe to call from module scope as well as from inside `render()`. The
       * verb is idempotent and reference-counted.
       *
       * @returns Resolves once the pipeline has finished switching.
       */
      enableEmotionDetection(enabled: boolean): Promise<void>;
      /**
       * Toggle 21-point hand landmarks and ML-classified gesture confidences
       * for up to two hands. Populates `viji.video.cv.hands[]` with `landmarks`,
       * `palm`, `bounds` (computed from landmarks), and `gestures`.
       *
       * Safe to call from module scope as well as from inside `render()`. The
       * verb is idempotent and reference-counted.
       *
       * @returns Resolves once the pipeline has finished switching.
       */
      enableHandTracking(enabled: boolean): Promise<void>;
      /**
       * Toggle 33-point BlazePose body landmarks (face / torso / arms / legs).
       * Populates `viji.video.cv.pose`.
       *
       * Safe to call from module scope as well as from inside `render()`. The
       * verb is idempotent and reference-counted.
       *
       * @returns Resolves once the pipeline has finished switching.
       */
      enablePoseDetection(enabled: boolean): Promise<void>;
      /**
       * Toggle per-pixel person/background segmentation mask. Populates
       * `viji.video.cv.segmentation`.
       *
       * Safe to call from module scope as well as from inside `render()`. The
       * verb is idempotent and reference-counted.
       *
       * @returns Resolves once the pipeline has finished switching.
       */
      enableBodySegmentation(enabled: boolean): Promise<void>;
      /** Returns the list of CV features currently enabled on this stream. */
      getActiveFeatures(): CVFeature[];
      /** Returns `true` if the CV worker is actively processing frames. */
      isProcessing(): boolean;
  }

  /**
   * The Viji API surface available to artist code as the global `viji` object.
   * Groups canvas access, timing, connected media APIs, user interaction, device
   * sensors, parameter helpers, and the `useContext()` selector.
   */
  interface VijiAPI {
      /** The `OffscreenCanvas` driving the scene. The host owns its lifecycle and dimensions; prefer `useContext()` over touching this directly. */
      canvas: OffscreenCanvas;
      /** 2D rendering context. `undefined` until `viji.useContext('2d')` is called; afterwards equals the cached context. */
      ctx?: OffscreenCanvasRenderingContext2D;
      /** WebGL rendering context. `undefined` until `viji.useContext('webgl')` or `'webgl2'` is called; afterwards equals the cached context. */
      gl?: WebGLRenderingContext | WebGL2RenderingContext;
      /** Current canvas width in pixels. Updates automatically when the host resizes the canvas; read every frame. */
      width: number;
      /** Current canvas height in pixels. Updates automatically when the host resizes the canvas; read every frame. */
      height: number;
      /** Seconds elapsed since the scene started (monotonically increasing float). Use for oscillations and absolute-time effects. */
      time: number;
      /** Seconds since the previous frame. Use for accumulation: movement, physics, fades, anything that should progress per second regardless of FPS. */
      deltaTime: number;
      /** Integer frame counter starting at `0` and incrementing by `1` each frame. */
      frameCount: number;
      /** Target frames-per-second for the host's current frame-rate mode (`60` for `'full'`, `30` for `'half'`). */
      fps: number;
      /** Real-time analysis of the main audio stream: volume, frequency bands, beat detection, spectral features. Fields are zeroed when no audio source is connected. */
      audio: AudioAPI;
      /** Main video stream API: pixel access, frame metadata, and computer-vision results (face, hands, pose, segmentation). */
      video: VideoAPI;
      /** Additional video streams provided by the host. These do not run CV processing; use them for raw pixel access only. */
      videoStreams: VideoAPI[];
      /** Additional audio streams (lightweight analysis: volume, bands, spectral). No beat detection; use the main `audio` for that. */
      audioStreams: AudioStreamAPI[];
      /** Mouse position, buttons, wheel, and per-frame movement state. Coordinates are in canvas pixels. */
      mouse: MouseAPI;
      /** Keyboard state: per-frame press/release queries, currently held keys, and modifier flags. */
      keyboard: KeyboardAPI;
      /** Multi-touch state: count, primary touch shortcut, and the per-frame `started` / `moved` / `ended` arrays. */
      touches: TouchAPI;
      /** Unified pointer (mouse + primary touch). Convenient single-point input that works on both desktop and mobile. */
      pointer: PointerAPI;
      /** Internal device sensors (motion + orientation) reported by the device running the scene. */
      device: DeviceSensorState;
      /** External connected devices reporting sensor / camera / audio. Each entry has its own `id`, `name`, and per-device API surface. */
      devices: DeviceState[];
      /**
       * Declares a numeric slider parameter. The host UI renders a draggable slider.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const radius = viji.slider(50, { min: 10, max: 200, step: 1, label: 'Radius' });
       * // ...
       * function render(viji) { drawCircle(radius.value); }
       */
      slider: (defaultValue: number, config: SliderConfig) => SliderParameter;
      /**
       * Declares a color parameter. Accepts hex strings, CSS color functions, or RGB / HSB objects;
       * the value is always normalized to a canonical lowercase hex string and exposes derived `.rgb` / `.hsb` accessors.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const tint = viji.color('#ff6600', { label: 'Tint' });
       * // tint.value -> '#ff6600', tint.rgb -> { r: 255, g: 102, b: 0 }, tint.hsb -> { h: 24, s: 100, b: 100 }
       */
      color: (defaultValue: ColorInput, config: ColorConfig) => ColorParameter;
      /**
       * Declares a boolean toggle parameter. The host UI renders an on/off switch.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const showTrail = viji.toggle(true, { label: 'Show Trail' });
       */
      toggle: (defaultValue: boolean, config: ToggleConfig) => ToggleParameter;
      /**
       * Declares a dropdown selection parameter. The element type of `options`
       * (`string` or `number`) determines the parameter's value type.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const shape = viji.select('circle', { options: ['circle', 'square', 'triangle'], label: 'Shape' });
       */
      select: (defaultValue: string | number, config: SelectConfig) => SelectParameter;
      /**
       * Declares a text input parameter. The host UI renders a single-line text field.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const caption = viji.text('Hello', { label: 'Caption', maxLength: 64 });
       */
      text: (defaultValue: string, config: TextConfig) => TextParameter;
      /**
       * Declares a precise numeric input parameter. Like `slider()` but the host UI
       * shows a number field instead of a draggable handle.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const count = viji.number(12, { min: 1, max: 64, step: 1, label: 'Count' });
       */
      number: (defaultValue: number, config: NumberConfig) => NumberParameter;
      /**
       * Declares an image upload parameter. The user-supplied image becomes available
       * as `value` (an `ImageBitmap`); in P5 scenes a `.p5` accessor is also added at runtime.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const tex = viji.image(null, { label: 'Texture' });
       * if (tex.value) ctx.drawImage(tex.value, 0, 0, viji.width, viji.height);
       */
      image: (defaultValue: null, config: ImageConfig) => ImageParameter;
      /**
       * Declares a momentary button parameter. `value` is `true` for one frame after
       * the user clicks, then auto-resets. Perfect for triggering discrete events.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const reset = viji.button({ label: 'Reset' });
       * function render(viji) { if (reset.value) state = initialState; }
       */
      button: (config: ButtonConfig) => ButtonParameter;
      /**
       * Declares a 2D coordinate parameter. Both `value.x` and `value.y` are in `-1..1`.
       * Must be called at the top level of the scene, never inside `render()`.
       *
       * @example
       * const origin = viji.coordinate({ x: 0, y: 0 }, { label: 'Origin' });
       * const cx = viji.width / 2 + origin.value.x * viji.width * 0.4;
       */
      coordinate: (defaultValue: CoordinateValue, config: CoordinateConfig) => CoordinateParameter;
      /**
       * Selects the 2D canvas rendering context. Returns the same instance on
       * subsequent calls and also stores it on `viji.ctx`. A canvas only supports
       * one context type. If a different context type was already requested,
       * the call returns `null`. Choose ONE type and use it for the entire scene.
       *
       * @example
       * const ctx = viji.useContext('2d');
       * ctx.fillRect(0, 0, viji.width, viji.height);
       */
      useContext(type: '2d'): OffscreenCanvasRenderingContext2D;
      /**
       * Selects a WebGL 1 rendering context. Returns the same instance on subsequent
       * calls and also stores it on `viji.gl`. A canvas only supports one context
       * type. If a different context type was already requested, the call returns
       * `null`. Choose ONE type and use it for the entire scene.
       */
      useContext(type: 'webgl'): WebGLRenderingContext;
      /**
       * Selects a WebGL 2 rendering context. Returns the same instance on subsequent
       * calls and also stores it on `viji.gl`. A canvas only supports one context
       * type. If a different context type was already requested, the call returns
       * `null`. Choose ONE type and use it for the entire scene.
       */
      useContext(type: 'webgl2'): WebGL2RenderingContext;
  }

  // Runtime global - the main viji object
  const viji: VijiAPI;
  
  // Function type aliases (artists define their own render/setup functions)
  type Render = (viji: VijiAPI) => void;
  type Setup = (viji: VijiAPI) => void;
}

// Module marker (enables top-level await in artist code)
export {};
