---
format: typebulb/v1
name: Sick
---

**code.tsx**

```tsx
import { App, Component, div, canvas } from "domeleon"

interface Particle {
  x: number
  y: number
  vx: number
  vy: number
  life: number
  maxLife: number
  hue: number
  size: number
}

class SickViz extends Component {
  #canvas: HTMLCanvasElement
  #ctx: CanvasRenderingContext2D
  #animationId: number
  #time = 0
  #particles: Particle[] = []
  #mouseX = 0
  #mouseY = 0
  #isMouseDown = false

  view() {
    return div({ class: "viz-container" },
      canvas({
        width: window.innerWidth,
        height: window.innerHeight,
        onMounted: (el) => {
          this.#canvas = el as HTMLCanvasElement
          this.#ctx = this.#canvas.getContext("2d", { alpha: false })

          this.#canvas.addEventListener("mousemove", this.handleMouseMove)
          this.#canvas.addEventListener("mousedown", this.handleMouseDown)
          this.#canvas.addEventListener("mouseup", this.handleMouseUp)
          this.#canvas.addEventListener("touchmove", this.handleTouchMove)
          window.addEventListener("resize", this.handleResize)

          this.startAnimation()

          return () => {
            cancelAnimationFrame(this.#animationId)
            this.#canvas.removeEventListener("mousemove", this.handleMouseMove)
            this.#canvas.removeEventListener("mousedown", this.handleMouseDown)
            this.#canvas.removeEventListener("mouseup", this.handleMouseUp)
            this.#canvas.removeEventListener("touchmove", this.handleTouchMove)
            window.removeEventListener("resize", this.handleResize)
          }
        }
      })
    )
  }

  handleMouseMove = (e: MouseEvent) => {
    this.#mouseX = e.clientX
    this.#mouseY = e.clientY
  }

  handleMouseDown = () => {
    this.#isMouseDown = true
  }

  handleMouseUp = () => {
    this.#isMouseDown = false
  }

  handleTouchMove = (e: TouchEvent) => {
    e.preventDefault()
    this.#mouseX = e.touches[0].clientX
    this.#mouseY = e.touches[0].clientY
  }

  handleResize = () => {
    this.#canvas.width = window.innerWidth
    this.#canvas.height = window.innerHeight
  }

  startAnimation() {
    const animate = () => {
      this.draw()
      this.#animationId = requestAnimationFrame(animate)
    }
    animate()
  }

  draw() {
    const ctx = this.#ctx
    const w = this.#canvas.width
    const h = this.#canvas.height

    this.#time += 0.01

    // Fade effect for trails
    ctx.fillStyle = "rgba(0, 0, 0, 0.08)"
    ctx.fillRect(0, 0, w, h)

    // Generate particles based on algorithmic patterns
    if (this.#particles.length < 500) {
      const count = this.#isMouseDown ? 5 : 1
      for (let i = 0; i < count; i++) {
        this.spawnParticle()
      }
    }

    // Draw geometric grid with modulation
    this.drawModulatedGrid(ctx, w, h)

    // Update and draw particles
    this.updateParticles(ctx)

    // Draw complex polyrhythmic circles
    this.drawPolyrhythmicCircles(ctx, w, h)
  }

  spawnParticle() {
    const angle = Math.random() * Math.PI * 2
    const speed = Math.random() * 2 + 1

    this.#particles.push({
      x: this.#mouseX || this.#canvas.width / 2,
      y: this.#mouseY || this.#canvas.height / 2,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      life: 1,
      maxLife: Math.random() * 100 + 50,
      hue: (this.#time * 50 + Math.random() * 60) % 360,
      size: Math.random() * 3 + 1
    })
  }

  updateParticles(ctx: CanvasRenderingContext2D) {
    for (let i = this.#particles.length - 1; i >= 0; i--) {
      const p = this.#particles[i]

      p.x += p.vx
      p.y += p.vy
      p.life--

      if (p.life <= 0) {
        this.#particles.splice(i, 1)
        continue
      }

      const alpha = p.life / p.maxLife
      ctx.fillStyle = `hsla(${p.hue}, 80%, 60%, ${alpha})`
      ctx.fillRect(p.x, p.y, p.size, p.size)
    }
  }

  drawModulatedGrid(ctx: CanvasRenderingContext2D, w: number, h: number) {
    ctx.strokeStyle = `hsla(${(this.#time * 30) % 360}, 70%, 50%, 0.15)`
    ctx.lineWidth = 1

    const gridSize = 40
    for (let x = 0; x < w; x += gridSize) {
      const offset = Math.sin(this.#time + x * 0.01) * 20
      ctx.beginPath()
      ctx.moveTo(x, 0)
      ctx.lineTo(x + offset, h)
      ctx.stroke()
    }

    for (let y = 0; y < h; y += gridSize) {
      const offset = Math.cos(this.#time + y * 0.01) * 20
      ctx.beginPath()
      ctx.moveTo(0, y)
      ctx.lineTo(w, y + offset)
      ctx.stroke()
    }
  }

  drawPolyrhythmicCircles(ctx: CanvasRenderingContext2D, w: number, h: number) {
    const cx = w / 2
    const cy = h / 2

    // Responsive: more circles on larger screens, fewer on smaller ones.
    // Keeps the existing circles identical (same formula), just adjusts how many are drawn.
    const maxRadius = Math.min(w, h) / 2 - 20
    const maxIndex = Math.floor((maxRadius - 80) / 60) // 80 = 50 base + 30 modulation
    const circleCount = Math.max(1, maxIndex + 1)

    for (let i = 0; i < circleCount; i++) {
      const radius = 50 + i * 60 + Math.sin(this.#time * (1 + i * 0.3)) * 30
      const hue = (this.#time * 20 + i * 72) % 360
      ctx.strokeStyle = `hsla(${hue}, 90%, 60%, 0.3)`
      ctx.lineWidth = 2
      ctx.beginPath()
      ctx.arc(cx, cy, radius, 0, Math.PI * 2)
      ctx.stroke()
    }
  }
}

new App({ root: new SickViz(), id: "app" })
```
**styles.css**

```css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

html[data-theme="dark"] {
  color-scheme: dark;
  --bg-color: #000000;
  --primary-hue: 200;
}

html[data-theme="light"] {
  color-scheme: light;
  --bg-color: #0a0a0a;
  --primary-hue: 280;
}

body {
  background: var(--bg-color);
  font-family: system-ui, -apple-system, sans-serif;
}

.viz-container {
  width: 100%;
  height: 100%;
  cursor: crosshair;
}

canvas {
  display: block;
}

```
**index.html**

```html
<div id="app"/>
```
**config.json**

```json
{
  "dependencies": {
    "domeleon": "^0.5.2"
  },
  "description": "Whoozy Visuals"
}
```
**notes.md**

````md
# Autechre-Inspired Visualization

A mesmerizing, algorithmic visualization inspired by Autechre's aesthetic.

## Features

- **Particle System**: Spawns particles that create flowing trails
- **Modulated Grid**: Sine/cosine wave distortions on grid lines
- **Polyrhythmic Circles**: Concentric circles pulsing at different frequencies
- **Interactive**: Mouse movement influences particle spawn position
- **Click/Hold**: Spawns more particles when mouse is held down
- **Fade Trails**: Creates ghosting effect for smooth motion blur

## Interaction

- Move mouse to change particle spawn location
- Click and hold to spawn more particles
- Touch-enabled for mobile devices

## Visual Style

Complex, glitchy, mathematical patterns reminiscent of Autechre's generative and algorithmic approach to electronic music. The visualization evolves continuously with layered rhythms and geometric precision.

## Example of Using Domeleon

```ts
import { App, Component, div, button, formField, inputRange, canvas } from "domeleon"

class Counter extends Component {
  count: number
  role: string
  #canvasEl: HTMLCanvasElement
  #canvasCtx: CanvasRenderingContext2D

  constructor(initial: number, role: string) {
    super()
    this.count = initial
    this.role = role
  }

  view() {
    return div({ class: "counter" },
      formField ({ // databound input
        target: this,
        inputFn: inputRange, // inputSelect, inputText, etc. (import "domeleon/maskito" for inputNumber)
        prop: () => this.count,
        label: this.role,
        fieldAttrs: { class: "field-class"},
        labelAttrs: { class: "label-class"},
        inputProps: { attrs: { min: 100, max: 250, step: 1 } }
      }),      
      button({ onClick: () => this.inc(+1) }, "+"), // always use closure to bind to preserve "this"
      button({ onClick: () => this.inc(-1) }, "-"),
      this.count,
      canvas({
        width: 400,
        height: 20,
        onMounted: el => {
          this.#canvasEl = el as HTMLCanvasElement
          this.#canvasCtx = this.#canvasEl.getContext("2d")
          return () => { // dispose
            this.#canvasEl = undefined
            this.#canvasCtx = undefined
          }
        }
      })
    )
  }
  
  inc(x: number) {
    this.count+=x
    this.update() // triggers render on app
  }

  // `onRendered` called after VDOM has created/updated DOM elements
  // useful for operations that require raw DOM access, such as canvas operations
  override onRendered () { 
     this.drawOnCanvas()
  }

  drawOnCanvas() {    
    const canvas = this.#canvasEl
    const cc = this.#canvasCtx
    cc.clearRect(0, 0, canvas.width, canvas.height)
    const barWidth = (this.count / 250) * canvas.width
    cc.fillStyle = "#4a90e2"
    cc.fillRect(0, 0, barWidth, canvas.height)
  }
}

class Game extends Component {
  wins = new Counter(10, "wins")
  losses = new Counter(5, "losses")

  view() {
    return div(
      this.wins.view(),
      this.losses.view()
    )
  }
}

new App({ root: new Game(), id: "app"})
```
````