Full Code of kitlangton/visual-effect for AI

main 2f910e4a2fe2
109 files
309.2 KB
79.7k tokens
Showing preview only (335K chars total). The displayed content is truncated. Use the JSON API for full output.
Repository: kitlangton/visual-effect
Branch: main
Commit: 2f910e4a2fe2
Files: 109
Total size: 309.2 KB

Directory structure:
gitextract_q3jrgzjr/

├── .gitignore
├── CLAUDE.md
├── LICENSE
├── README.md
├── app/
│   ├── ClientAppContent.tsx
│   ├── [exampleId]/
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── biome.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.mjs
├── public/
│   ├── _headers
│   ├── generate-favicons.html
│   ├── robots.txt
│   ├── site.webmanifest
│   └── sitemap.xml
├── scripts/
│   └── generate-og-images.tsx
├── src/
│   ├── AppContent.tsx
│   ├── VisualEffect.test.ts
│   ├── VisualEffect.ts
│   ├── VisualRef.ts
│   ├── VisualScope.ts
│   ├── animations.ts
│   ├── components/
│   │   ├── CodeBlock.tsx
│   │   ├── HeaderView.tsx
│   │   ├── ScheduleTimeline.tsx
│   │   ├── Timer.tsx
│   │   ├── display/
│   │   │   ├── EffectExample.tsx
│   │   │   ├── RefDisplay.tsx
│   │   │   └── index.ts
│   │   ├── effect/
│   │   │   ├── EffectContainer.tsx
│   │   │   ├── EffectContent.tsx
│   │   │   ├── EffectLabel.tsx
│   │   │   ├── EffectNode.tsx
│   │   │   ├── EffectOverlay.tsx
│   │   │   ├── index.ts
│   │   │   ├── nodeVariants.ts
│   │   │   ├── taskUtils.ts
│   │   │   └── useEffectMotion.ts
│   │   ├── feedback/
│   │   │   ├── DeathBubble.tsx
│   │   │   ├── EffectLogo.tsx
│   │   │   ├── FailureBubble.tsx
│   │   │   ├── FloatingHighlight.tsx
│   │   │   ├── NotificationBubble.tsx
│   │   │   └── index.ts
│   │   ├── index.ts
│   │   ├── layout/
│   │   │   ├── NavigationSidebar.tsx
│   │   │   └── PageHeader.tsx
│   │   ├── renderers/
│   │   │   ├── ArrayResult.tsx
│   │   │   ├── BasicRenderers.tsx
│   │   │   ├── EmojiResult.tsx
│   │   │   ├── RenderableResult.ts
│   │   │   ├── TemperatureResult.tsx
│   │   │   └── index.ts
│   │   ├── scope/
│   │   │   ├── FinalizerCard.tsx
│   │   │   ├── ScopeStack.tsx
│   │   │   └── utils.ts
│   │   └── ui/
│   │       ├── QuickOpen.tsx
│   │       ├── SegmentedControl.tsx
│   │       ├── VolumeToggle.tsx
│   │       └── index.ts
│   ├── constants/
│   │   ├── colors.ts
│   │   └── dimensions.ts
│   ├── examples/
│   │   ├── effect-acquire-release.tsx
│   │   ├── effect-add-finalizer.tsx
│   │   ├── effect-all-short-circuit.tsx
│   │   ├── effect-all.tsx
│   │   ├── effect-die.tsx
│   │   ├── effect-eventually.tsx
│   │   ├── effect-fail.tsx
│   │   ├── effect-firstsuccessof.tsx
│   │   ├── effect-foreach.tsx
│   │   ├── effect-orelse.tsx
│   │   ├── effect-partition.tsx
│   │   ├── effect-promise.tsx
│   │   ├── effect-race.tsx
│   │   ├── effect-raceall.tsx
│   │   ├── effect-repeat-spaced.tsx
│   │   ├── effect-repeat-while-output.tsx
│   │   ├── effect-retry-exponential.tsx
│   │   ├── effect-retry-recurs.tsx
│   │   ├── effect-sleep.tsx
│   │   ├── effect-succeed.tsx
│   │   ├── effect-sync.tsx
│   │   ├── effect-timeout.tsx
│   │   ├── effect-validate.tsx
│   │   ├── helpers.ts
│   │   ├── ref-make.tsx
│   │   └── ref-update-and-get.tsx
│   ├── hooks/
│   │   ├── useOptionKey.ts
│   │   ├── useStateTransition.ts
│   │   ├── useVisualEffects.ts
│   │   └── useVisualScope.ts
│   ├── lib/
│   │   ├── example-types.ts
│   │   └── examples-manifest.ts
│   ├── shared/
│   │   ├── appItems.ts
│   │   └── idUtils.ts
│   ├── sounds/
│   │   └── TaskSounds.ts
│   └── theme.ts
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.scripts.json
├── vitest.config.ts
└── wrangler.jsonc

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
.next
out/
.vscode
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.vercel

# Generated OG images
public/og/
tsconfig.tsbuildinfo
.mcp.json
.claude


================================================
FILE: CLAUDE.md
================================================
# Visual Effect - Codebase Documentation

## Overview

Visual Effect is an interactive visualization tool for the Effect library that demonstrates how Effect operations execute over time. Built with Next.js 15 and React 19, it provides animated visual representations of Effect constructors and combinators with synchronized sound effects, making it easier to understand their behavior.

**In this house, we use bun.** All package management and script execution should use `bun` commands, not `npm` or `node`.

## Core Concepts

### 1. VisualEffect

The `VisualEffect` class is the heart of the visualization system. It wraps Effect operations and tracks their execution state for visualization purposes.

```typescript
// Creating a visual effect
const myEffect = visualEffect("taskName", Effect.succeed(42));
```

Key features:
- **State tracking**: idle → running → completed/failed/interrupted/death
- **Observable hooks**: React components subscribe via `useVisualEffectState`, `useVisualEffectNotification`, or `useVisualEffectSubscription`
- **Effect caching**: Prevents re-execution of already completed effects
- **Timer support**: Captures start/end timestamps when `showTimer` is enabled
- **Notification helpers**: Effects can publish contextual messages through `notify(...)`
- **Sound triggers**: Automatically plays sounds on state transitions

### 2. EffectNode Component

The `EffectNode` component renders individual effects as animated circles with:
- Different colors for different states (idle, running, completed, failed)
- Pulsing animations during execution
- Result display using the renderer system
- Automatic width expansion when results overflow the default size
- Overlay feedback for errors and notifications

### 3. Renderer System

Results are displayed using a flexible renderer pattern:

```typescript
class MyResult implements RenderableResult {
  constructor(public value: any) {}
  
  render() {
    return <div>{this.value}</div>;
  }
}
```

Built-in renderers:
- `NumberResult` - Simple number display
- `StringResult` - Simple string display
- `BooleanResult` - True/false text badge
- `TemperatureResult` - Temperature with a trailing ° symbol
- `ObjectResult` - JSON stringified objects
- `ArrayResult` - Animated array summary (length indicator)
- `EmojiResult` - Emoji-based results with enhanced visual appeal

### 4. Effect Examples

Each example follows a consistent pattern:

```typescript
export function EffectExampleName() {
  // 1. Create individual effects with memoization
  const effect1 = useMemo(() => visualEffect("name", effect), []);
  
  // 2. Create composed effect if needed
  const resultEffect = useMemo(() => {
    const composed = Effect.all([effect1.effect, effect2.effect]);
    return new VisualEffect("result", composed, [effect1, effect2]);
  }, [effect1, effect2]);
  
  // 3. Define code snippet and highlight mappings
  const codeSnippet = `...`;
  const effectHighlightMap = { ... };
  
  // 4. Return EffectExample component
  return <EffectExample ... />;
}
```

## Key Patterns

### 1. Jittered Delays

All examples use realistic, non-deterministic delays to simulate real-world conditions:

```typescript
export function getWeather(location?: string) {
  return Effect.gen(function* () {
    const delay = getDelay(500, 900); // Random 500-900ms
    yield* Effect.sleep(delay);
    return new TemperatureResult(...);
  });
}
```

### 2. Responsive Design

- Layout built with Tailwind utility classes and Motion; flex containers wrap naturally on small screens
- Sidebar navigation collapses on narrow viewports while the main content remains accessible
- Typography and spacing scale using relative units for readability across devices

### 3. State Management

- Each `VisualEffect` manages its own state
- React components subscribe via `useVisualEffectState`, `useVisualEffectNotification`, or `useVisualEffectSubscription`
- Lightweight hooks (`useOptionKey`, `useStateTransition`, `useVisualScope`) handle UI-specific state
- No global state management for effect execution
- Effects persist across component re-renders

### 4. Animation System

- Uses Motion (Framer Motion successor) for smooth transitions
- Spring animations for natural movement with configurable physics
- Different animations for different state transitions
- Hardware-accelerated transforms
- Dedicated sequences for running jitter, failure shakes, and death glitches

### 5. Sound System

The application includes a synthesized sound system using Tone.js:

- **Distinct cues**: Success, running, failure, interruption, reset, death, ref updates, finalizers, and notifications all receive unique tones
- **Shared processing**: A centralized `taskSounds` module initializes synths, routing, and reverb once and gates playback behind a mute flag
- **User controls**: The header exposes an ON/OFF toggle that updates the mute state and plays a confirmation chime when sound is enabled
- **Integration**: `VisualEffect.setState()` and companion helpers trigger the appropriate cues during state transitions

## File Structure

```
app/                         # Next.js App Router
├── layout.tsx              # Root layout with metadata
├── page.tsx                # Home page
├── [exampleId]/
│   └── page.tsx            # Individual example pages
└── ClientAppContent.tsx    # Client-side app content

src/
├── animations.ts           # Shared animation tokens
├── AppContent.tsx          # Main app component
├── components/
│   ├── CodeBlock.tsx       # Syntax-highlighted code
│   ├── HeaderView.tsx      # Example headers + controls
│   ├── ScheduleTimeline.tsx # Scheduling visualizer
│   ├── Timer.tsx           # Elapsed time labels
│   ├── display/            # Display components
│   │   ├── EffectExample.tsx # Main example wrapper
│   │   └── RefDisplay.tsx  # Ref visualizations
│   ├── effect/             # Effect visualization primitives
│   │   ├── EffectNode.tsx
│   │   ├── EffectOverlay.tsx
│   │   ├── taskUtils.ts
│   │   └── useEffectMotion.ts
│   ├── feedback/           # User feedback
│   │   ├── DeathBubble.tsx
│   │   ├── FailureBubble.tsx
│   │   └── NotificationBubble.tsx
│   ├── layout/             # Layout components
│   │   ├── NavigationSidebar.tsx
│   │   └── PageHeader.tsx
│   ├── renderers/          # Result rendering system
│   │   ├── ArrayResult.tsx
│   │   ├── BasicRenderers.tsx
│   │   ├── EmojiResult.tsx
│   │   └── TemperatureResult.tsx
│   ├── scope/
│   │   ├── FinalizerCard.tsx
│   │   └── ScopeStack.tsx
│   └── ui/
│       ├── QuickOpen.tsx
│       ├── SegmentedControl.tsx
│       └── VolumeToggle.tsx
├── constants/
│   ├── colors.ts
│   └── dimensions.ts
├── examples/               # Effect examples
│   ├── helpers.ts          # Shared utilities
│   ├── effect-*.tsx        # Effect examples
│   └── ref-*.tsx          # Ref examples
├── hooks/                  # Custom hooks
│   ├── useOptionKey.ts     # Option key detection
│   ├── useStateTransition.ts # Effect transition tracking
│   └── useVisualScope.ts   # Scope management
├── lib/                    # Library code
│   ├── example-types.ts    # Type definitions
│   └── examples-manifest.ts # Example registry
├── shared/                 # Shared utilities
│   ├── appItems.ts
│   └── idUtils.ts
├── sounds/
│   └── TaskSounds.ts       # Synthesized sound system
├── theme.ts                # Theme tokens
├── VisualEffect.ts         # Core effect visualization
├── VisualRef.ts           # Ref visualization
├── VisualScope.ts         # Scope visualization
└── VisualEffect.test.ts    # Unit tests
```

## Design Decisions

### 1. No External State Management
Each task manages its own state internally, avoiding complexity and making examples self-contained.

### 2. Effect-First Design
The visualization follows Effect's execution model closely - tasks only run when their effect is executed.

### 3. Realistic Timing
All examples use jittered delays to demonstrate non-deterministic behavior, especially important for race conditions.

### 4. Mobile-Responsive
The entire UI adapts to mobile screens without compromising the desktop experience.

### 5. Type Safety
Strict TypeScript configuration catches errors at compile time, including:
- `isolatedModules` for Next.js compatibility
- `noUncheckedIndexedAccess` for array safety
- `exactOptionalPropertyTypes` for precise optional handling
- Effect-specific TypeScript plugin for enhanced type checking

### 6. Audio Experience
Sounds are designed to enhance understanding without being intrusive:
- Short, focused cues map directly to running, completion, failure, interruption, and reset events
- A centralized sound module keeps the palette cohesive and manages initialization/muting
- Automatic sound on state transitions with respectful default levels
- User-friendly mute control without in-app volume sliders (defers to system volume)

## Common Operations

### Adding a New Effect Example

1. Create a new file in `src/examples/`
2. Use the `getWeather` helper for consistent behavior
3. Follow the example pattern (memoized effects, code snippet, highlight map)
4. Add to the examples manifest in `src/lib/examples-manifest.ts`
5. Generate OG images with `bun run generate-og-images`

### Creating Custom Renderers

1. Implement the `RenderableResult` interface in `src/components/renderers/`
2. Add a `render()` method returning JSX
3. Export from the renderers index file
4. Use in your effect: `Effect.map(value => new MyRenderer(value))`

### Modifying Animations

Look in `EffectNode.tsx` and `animations.ts` for animation configurations:
- Spring settings including `defaultSpring` for MotionConfig
- Color transitions in state change logic
- Timing constants in individual components
- All animation tokens centralized in `animations.ts`

## Best Practices

1. **Always memoize effects** - Prevents recreation on every render
2. **Use built-in helpers** - `getWeather()` for consistency
3. **Keep effects pure** - Side effects only for visualization
4. **Test on mobile** - Ensure responsive behavior works
5. **Follow the pattern** - Consistency makes the codebase maintainable
6. **Use proper accessibility** - Provide ARIA labels, focus states, and keyboard-friendly controls
7. **Optimize bundle size** - Lazy load examples and use code splitting

## Copy Style Guide

### Text and Descriptions

**Example Descriptions:**
- Use imperative mood (e.g., "Create", "Run", "Compose")
- No ending punctuation (periods, exclamation marks)
- Start with action verbs for consistency
- Keep descriptions concise but informative

**Good Examples:**
- "Run multiple effects concurrently and compose their results"
- "Interrupt a running effect after a specified duration"
- "Accumulate validation errors instead of failing fast"

**Avoid:**
- Present tense ("Creates", "Runs", "Composes")
- Ending punctuation ("Create a new task.")
- Passive voice ("A task is created")

**UI Text:**
- Use proper articles (a, an, the) in prose
- Maintain consistent tone throughout the application
- Keep instructions clear and action-oriented
- Use sentence case for buttons and labels

**Error Messages:**
- Start with the action or context
- Be specific about what went wrong
- Provide actionable next steps when possible

**Code Comments:**
- Use present tense for describing what code does
- Keep comments concise and focused on the "why"
- Avoid obvious comments that just restate the code

## Architecture Decisions

### 1. Next.js App Router
Migrated from Vite to Next.js 15 for:
- Better SEO with server-side rendering
- Individual pages for each example
- Automatic code splitting and optimization
- Built-in image optimization

### 2. Component Architecture
Organized components by domain:
- `display/` - Main display logic
- `effect/` - Effect visualization specifics
- `feedback/` - User feedback components
- `layout/` - Layout and navigation
- `renderers/` - Result rendering system
- `scope/` - Scope and finalizer visualization
- `ui/` - Reusable UI components
- Top-level helpers (`CodeBlock`, `HeaderView`, `Timer`, `ScheduleTimeline`) live alongside these folders

### 3. New Visualization Types
Expanded beyond basic effects to include:
- **Ref visualization** with `VisualRef` class
- **Scope visualization** with finalizer tracking
- **Quick-open modal** and link-copy affordances for faster exploration
- **Lazy loading** for better performance

### 4. Enhanced Developer Experience
- Biome for linting and formatting (replaced ESLint/Prettier)
- TypeScript strict mode with Effect language service
- Automated OG image generation for social sharing
- Comprehensive example manifest system


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2025 Kit Langton

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Visual Effect

Interactive visualizations for [Effect](https://github.com/Effect-TS/effect) programs, built with Effect itself.

## What is this?

An interactive Next.js app that visualizes how Effect operations execute over time. Watch effects run, compose, race, and handle errors with animated nodes and synchronized sound feedback.

## Local Development

```bash
pnpm install
pnpm dev
```

## License

MIT — see LICENSE


================================================
FILE: app/ClientAppContent.tsx
================================================
"use client"

import dynamic from "next/dynamic"

const AppContent = dynamic(
  () => import("../src/AppContent").then(mod => ({ default: mod.AppContent })),
  {
    ssr: false,
  },
)

export default function ClientAppContent() {
  return <AppContent />
}


================================================
FILE: app/[exampleId]/page.tsx
================================================
/* eslint-disable react-refresh/only-export-components */
import type { Metadata } from "next"
import { examplesManifest, getExampleMeta } from "../../src/lib/examples-manifest"

export const dynamicParams = false

// Build-time helper
export async function generateStaticParams() {
  return examplesManifest.map(example => ({ exampleId: example.id }))
}

// Dynamic metadata generation for each example route
export async function generateMetadata({
  params,
}: {
  params: Promise<{ exampleId: string }>
}): Promise<Metadata> {
  const { exampleId } = await params

  try {
    const meta = getExampleMeta(exampleId)
    if (!meta) {
      return {
        title: "Example Not Found - Visual Effect",
        description: "The requested example could not be found",
      }
    }

    const title = `${meta.name}${meta.variant ? ` ${meta.variant}` : ""} - Visual Effect`

    return {
      title,
      description: meta.description,
      openGraph: {
        title,
        description: meta.description,
        url: `https://effect.kitlangton.com/${exampleId}`,
        siteName: "Visual Effect",
        images: [
          {
            url: `/og/${exampleId}.png`,
            width: 1200,
            height: 630,
            alt: title,
          },
        ],
        locale: "en_US",
        type: "website",
      },
      twitter: {
        card: "summary_large_image",
        title,
        description: meta.description,
        images: [`/og/${exampleId}.png`],
      },
    }
  } catch {
    // Fallback to default metadata
    return {
      title: "Visual Effect - Interactive Effect Playground",
      description: "Interactive examples of TypeScript's beautiful Effect library",
    }
  }
}

export default function ExamplePage() {
  return null // nothing mounts, so no state loss
}


================================================
FILE: app/globals.css
================================================
@import "tailwindcss";

/* Dark mode scrollbar for all elements */
* {
  scrollbar-width: thin;
  scrollbar-color: #262626 #0a0a0a;
}

/* Webkit scrollbar styling */
.sidebar-scrollbar::-webkit-scrollbar,
body::-webkit-scrollbar,
html::-webkit-scrollbar,
*::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

.sidebar-scrollbar::-webkit-scrollbar-track,
body::-webkit-scrollbar-track,
html::-webkit-scrollbar-track,
*::-webkit-scrollbar-track {
  background: #0a0a0a;
}

.sidebar-scrollbar::-webkit-scrollbar-thumb,
body::-webkit-scrollbar-thumb,
html::-webkit-scrollbar-thumb,
*::-webkit-scrollbar-thumb {
  background: #262626;
  border-radius: 3px;
}

.sidebar-scrollbar::-webkit-scrollbar-thumb:hover,
body::-webkit-scrollbar-thumb:hover,
html::-webkit-scrollbar-thumb:hover,
*::-webkit-scrollbar-thumb:hover {
  background: #404040;
}


================================================
FILE: app/layout.tsx
================================================
/* eslint-disable react-refresh/only-export-components */
import "./globals.css"
import ClientAppContent from "./ClientAppContent"

export const metadata = {
  title: "Visual Effect - Interactive Effect Playground",
  description:
    "An interactive visualization tool for the Effect library that demonstrates how Effect operations execute over time with animated visual representations and synchronized sound effects.",
  metadataBase: new URL("https://effect.kitlangton.com"),
  openGraph: {
    title: "Visual Effect - Interactive Effect Playground",
    description: "Interactive examples of TypeScript's beautiful Effect library",
    url: "https://effect.kitlangton.com/",
    siteName: "Visual Effect",
    images: [
      {
        url: "/og-image.png",
        width: 1200,
        height: 630,
        alt: "Visual Effect - Interactive Effect Playground",
      },
    ],
    locale: "en_US",
    type: "website",
  },
  twitter: {
    card: "summary_large_image",
    title: "Visual Effect - Interactive Effect Playground",
    description: "Interactive examples of TypeScript's beautiful Effect library",
    images: ["/og-image.png"],
  },
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      "max-video-preview": -1,
      "max-image-preview": "large",
      "max-snippet": -1,
    },
  },
  verification: {
    google: "google-verification-code", // Add your Google verification code if needed
  },
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className="bg-neutral-950 text-white">
      {/* `vsc-initialized` is injected by some VS Code extensions after SSR; suppress hydration mismatch warnings */}
      <body>
        <ClientAppContent />
        {children}
      </body>
    </html>
  )
}


================================================
FILE: app/page.tsx
================================================
export default function HomePage() {
  return null
}


================================================
FILE: biome.json
================================================
{
  "$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
  "assist": { "actions": { "source": { "organizeImports": "on" } } },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "useExhaustiveDependencies": "off"
      },
      "suspicious": {
        "noArrayIndexKey": "off",
        "noExplicitAny": "warn"
      },
      "style": {
        "noNonNullAssertion": "warn",
        "useNodejsImportProtocol": "error"
      },
      "complexity": {
        "useLiteralKeys": "error"
      },
      "a11y": {
        "noStaticElementInteractions": "off",
        "noSvgWithoutTitle": "off",
        "useKeyWithClickEvents": "off"
      }
    }
  },
  "formatter": {
    "enabled": true,
    "formatWithErrors": false,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineEnding": "lf",
    "lineWidth": 100
  },
  "javascript": {
    "formatter": {
      "semicolons": "asNeeded",
      "quoteStyle": "double",
      "jsxQuoteStyle": "double",
      "trailingCommas": "all",
      "arrowParentheses": "asNeeded"
    }
  },
  "json": {
    "parser": {
      "allowComments": true
    }
  },
  "files": {
    "includes": [
      "**",
      "!**/node_modules",
      "!**/out",
      "!**/dist",
      "!**/.next",
      "!**/coverage",
      "!**/*.min.js",
      "!**/pnpm-lock.yaml"
    ]
  },
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  }
}


================================================
FILE: next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./out/types/routes.d.ts" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.


================================================
FILE: next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
  turbopack: {},
  output: "export",
  trailingSlash: true,
  images: {
    unoptimized: true,
  },
  distDir: "out",
  reactStrictMode: true,
  experimental: {
    esmExternals: true,
  },
  webpack: (config, { isServer }) => {
    // Disable module concatenation to fix "Unexpected end of JSON input" errors
    config.optimization.concatenateModules = false

    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        path: false,
        crypto: false,
      }
    }

    return config
  },
  transpilePackages: ["motion"],
}

export default nextConfig


================================================
FILE: package.json
================================================
{
  "name": "visual-effect",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "sideEffects": false,
  "license": "MIT",
  "description": "Interactive visual state machine for Effect (effect-ts) with a Next.js + React demo and Tone.js sound cues.",
  "repository": {
    "type": "git",
    "url": "https://github.com/kitlangton/visual-effect.git"
  },
  "homepage": "https://github.com/kitlangton/visual-effect#readme",
  "bugs": {
    "url": "https://github.com/kitlangton/visual-effect/issues"
  },
  "author": "Kit Langton",
  "keywords": [
    "effect",
    "effect-ts",
    "react",
    "nextjs",
    "tonejs",
    "state-machine",
    "visualization",
    "typescript"
  ],
  "engines": {
    "node": ">=20",
    "bun": ">=1.0.0"
  },
  "scripts": {
    "dev": "next dev",
    "prebuild": "tsx scripts/generate-og-images.tsx",
    "build": "next build",
    "cf:build": "bun run build",
    "cf:deploy": "wrangler deploy",
    "deploy": "bun run cf:build && bun run cf:deploy",
    "start": "next start",
    "lint": "biome lint .",
    "lint-fix": "biome lint . --apply",
    "format": "biome format . --write",
    "check": "biome check .",
    "check-fix": "biome check . --write",
    "preview": "next start",
    "test": "vitest",
    "typecheck": "tsc --noEmit",
    "type-check": "tsc --noEmit",
    "generate-og-images": "tsx scripts/generate-og-images.tsx",
    "fix": "biome check . --write && bun run typecheck",
    "clean": "biome format . --write && biome lint . --write && bun run typecheck",
    "verify": "biome check . && bun run typecheck && bun run test",
    "ready": "bun run clean && bun run build"
  },
  "dependencies": {
    "@phosphor-icons/react": "^2.1.10",
    "effect": "^3.19.14",
    "motion": "^12.27.1",
    "next": "^16.1.3",
    "prism-react-renderer": "^2.4.1",
    "react": "^19.2.3",
    "react-dom": "^19.2.3",
    "tone": "^15.1.22"
  },
  "devDependencies": {
    "@biomejs/biome": "2.3.11",
    "@effect/language-service": "^0.71.0",
    "@resvg/resvg-js": "^2.6.2",
    "@tailwindcss/postcss": "^4.1.18",
    "@types/node": "^25.0.9",
    "@types/react": "^19.2.8",
    "@types/react-dom": "^19.2.3",
    "@vitest/ui": "^4.0.17",
    "jsdom": "^27.4.0",
    "satori": "^0.19.1",
    "tailwindcss": "^4.1.18",
    "tsx": "^4.21.0",
    "typescript": "~5.9.3",
    "vitest": "^4.0.17",
    "wrangler": "^4.107.0"
  },
  "packageManager": "bun@1.3.0",
  "trustedDependencies": [
    "@tailwindcss/oxide"
  ]
}


================================================
FILE: postcss.config.mjs
================================================
export default {
  plugins: {
    "@tailwindcss/postcss": {},
  },
}


================================================
FILE: public/_headers
================================================
/*
  X-Content-Type-Options: nosniff
  X-Frame-Options: DENY
  X-XSS-Protection: 1; mode=block

/_next/static/*
  Cache-Control: public, max-age=31536000, immutable

/og/*
  Cache-Control: public, immutable, no-transform, max-age=31536000

/fonts/*
  Cache-Control: public, immutable, max-age=31536000


================================================
FILE: public/generate-favicons.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>Generate Favicons</title>
</head>
<body>
    <h1>Generate PNG Favicons from SVG</h1>
    <p>Open this file in a browser, then right-click and save each image:</p>
    
    <h2>favicon-32x32.png</h2>
    <img src="/favicon.svg" width="32" height="32" style="image-rendering: crisp-edges; background: black;">
    
    <h2>favicon-16x16.png</h2>
    <img src="/favicon.svg" width="16" height="16" style="image-rendering: crisp-edges; background: black;">
    
    <h2>apple-touch-icon.png (180x180)</h2>
    <img src="/favicon.svg" width="180" height="180" style="image-rendering: crisp-edges; background: black;">
    
    <h2>android-chrome-192x192.png</h2>
    <img src="/favicon.svg" width="192" height="192" style="image-rendering: crisp-edges; background: black;">
    
    <h2>android-chrome-512x512.png</h2>
    <img src="/favicon.svg" width="512" height="512" style="image-rendering: crisp-edges; background: black;">
    
    <script>
        // You can use this script with a canvas to generate PNGs programmatically
        // For now, just save these manually or use an online converter
    </script>
</body>
</html>

================================================
FILE: public/robots.txt
================================================
# robots.txt for Visual Effect

User-agent: *
Allow: /

# Sitemap location
Sitemap: https://effect.kitlangton.com/sitemap.xml

================================================
FILE: public/site.webmanifest
================================================
{
  "name": "Visual Effect - Interactive Effect Library Visualizer",
  "short_name": "Visual Effect",
  "description": "An interactive visualization tool for the Effect library that demonstrates how Effect operations execute over time with animated visual representations and synchronized sound effects.",
  "theme_color": "#000000",
  "background_color": "#000000",
  "display": "standalone",
  "start_url": "/",
  "scope": "/",
  "orientation": "portrait-primary"
}


================================================
FILE: public/sitemap.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://effect.kitlangton.com/</loc>
    <lastmod>2025-01-05</lastmod>
    <changefreq>weekly</changefreq>
    <priority>1.0</priority>
  </url>
</urlset>

================================================
FILE: scripts/generate-og-images.tsx
================================================
/* eslint-disable react-refresh/only-export-components */

import fs from "node:fs/promises"
import path from "node:path"
import { fileURLToPath } from "node:url"
import { Resvg } from "@resvg/resvg-js"
// biome-ignore lint/correctness/noUnusedImports: We actually need it, liar.
import React from "react"
import satori from "satori"
import { examplesManifest } from "../src/lib/examples-manifest.js"

const __dirname = path.dirname(fileURLToPath(import.meta.url))

// No need for createExampleId anymore - we use the id from meta

// OG Image Component
function OGImage({
  description,
  name,
  variant,
}: {
  name: string
  variant?: string
  description: string
}) {
  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        display: "flex",
        flexDirection: "column",
        justifyContent: "space-between",
        padding: 80,
        background: "linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%)",
        fontFamily: "Inter",
      }}
    >
      {/* Header */}
      <div style={{ display: "flex", alignItems: "center", gap: 20 }}>
        <div
          style={{
            width: 60,
            height: 60,
            borderRadius: 12,
            background: "#3b82f6",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <svg width="32" height="32" fill="white" viewBox="0 0 256 256">
            <path d="M240,128a15.79,15.79,0,0,1-10.5,15l-63.44,23.07L143,229.5a16,16,0,0,1-30,0L89.94,166.06,26.5,143a16,16,0,0,1,0-30L89.94,89.94,113,26.5a16,16,0,0,1,30,0l23.07,63.44L229.5,113A15.79,15.79,0,0,1,240,128Z"></path>
          </svg>
        </div>
        <div
          style={{
            color: "#6b7280",
            fontSize: 24,
            fontWeight: 700,
            letterSpacing: "0.05em",
          }}
        >
          VISUAL EFFECT
        </div>
      </div>

      {/* Main Content */}
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: 20,
          flex: 1,
          justifyContent: "center",
        }}
      >
        <div style={{ display: "flex", alignItems: "baseline", gap: 16 }}>
          <h1
            style={{
              fontSize: 72,
              color: "white",
              margin: 0,
              fontWeight: 700,
              lineHeight: 1,
            }}
          >
            {name}
          </h1>
          {variant && (
            <span
              style={{
                fontSize: 48,
                color: "#6b7280",
                fontWeight: 500,
              }}
            >
              {variant}
            </span>
          )}
        </div>

        <p
          style={{
            fontSize: 32,
            color: "#9ca3af",
            margin: 0,
            lineHeight: 1.4,
            maxWidth: "90%",
          }}
        >
          {description}
        </p>
      </div>

      {/* Footer */}
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          paddingTop: 40,
          borderTop: "1px solid #374151",
        }}
      >
        <div style={{ color: "#6b7280", fontSize: 20 }}>Interactive Effect Examples</div>
        <div style={{ color: "#6b7280", fontSize: 20 }}>effect.kitlangton.com</div>
      </div>
    </div>
  )
}

async function generateOGImages() {
  console.log("🎨 Generating Open Graph images...")

  // Ensure output directory exists
  const outputDir = path.join(__dirname, "..", "public", "og")
  await fs.mkdir(outputDir, { recursive: true })

  // Load font
  const interBoldPath = path.join(__dirname, "..", "public", "fonts", "Inter-Bold.ttf")
  const interRegularPath = path.join(__dirname, "..", "public", "fonts", "Inter-Regular.ttf")

  // Check if fonts exist, if not, download them
  let interBold: ArrayBuffer
  let interRegular: ArrayBuffer

  try {
    const boldBuffer = await fs.readFile(interBoldPath)
    const regularBuffer = await fs.readFile(interRegularPath)
    interBold = boldBuffer.buffer.slice(
      boldBuffer.byteOffset,
      boldBuffer.byteOffset + boldBuffer.byteLength,
    ) as ArrayBuffer
    interRegular = regularBuffer.buffer.slice(
      regularBuffer.byteOffset,
      regularBuffer.byteOffset + regularBuffer.byteLength,
    ) as ArrayBuffer
  } catch {
    console.log("⚠️  Fonts not found locally, using system fonts as fallback")
    // Use a minimal font as fallback
    interBold = new ArrayBuffer(0)
    interRegular = new ArrayBuffer(0)
  }

  // Generate all images in parallel
  const imagePromises = examplesManifest.map(async example => {
    const id = example.id
    console.log(`  📸 Generating ${id}.png`)

    try {
      const svg = await satori(
        <OGImage
          name={example.name}
          {...(example.variant ? { variant: example.variant } : {})}
          description={example.description}
        />,
        {
          width: 1200,
          height: 630,
          fonts:
            interBold.byteLength > 0
              ? [
                  {
                    name: "Inter",
                    data: interBold,
                    weight: 700,
                    style: "normal",
                  },
                  {
                    name: "Inter",
                    data: interRegular,
                    weight: 400,
                    style: "normal",
                  },
                ]
              : [],
        },
      )

      const resvg = new Resvg(svg, {
        fitTo: {
          mode: "width",
          value: 1200,
        },
      })

      const pngData = resvg.render()
      const pngBuffer = pngData.asPng()

      await fs.writeFile(path.join(outputDir, `${id}.png`), pngBuffer)

      return { id, success: true }
    } catch (error) {
      console.error(`  ❌ Failed to generate ${id}.png:`, error)
      return { id, success: false, error }
    }
  })

  // Wait for all images to complete
  const results = await Promise.allSettled(imagePromises)

  // Count successes and failures
  const completed = results.filter(r => r.status === "fulfilled").length
  const failed = results.filter(r => r.status === "rejected").length

  if (failed > 0) {
    console.log(`⚠️  ${failed} images failed to generate`)
  }

  console.log(`✅ Generated ${completed} Open Graph images successfully`)
}

// Run the script
generateOGImages().catch(console.error)


================================================
FILE: src/AppContent.tsx
================================================
"use client"

import {
  ArrowClockwiseIcon,
  HashStraightIcon,
  HeartIcon,
  PlayIcon,
  SkullIcon,
  StopIcon,
} from "@phosphor-icons/react"
import { MotionConfig } from "motion/react"
import { usePathname } from "next/navigation"
import { Fragment, useCallback, useEffect, useMemo, useState } from "react"

// Examples
import EffectAcquireRelease from "@/examples/effect-acquire-release"
import EffectAddFinalizer from "@/examples/effect-add-finalizer"
import EffectAll from "@/examples/effect-all"
import EffectAllShortCircuit from "@/examples/effect-all-short-circuit"
import EffectDie from "@/examples/effect-die"
import EffectEventually from "@/examples/effect-eventually"
import EffectFail from "@/examples/effect-fail"
import EffectFirstSuccessOf from "@/examples/effect-firstsuccessof"
import EffectForEach from "@/examples/effect-foreach"
import EffectOrElse from "@/examples/effect-orelse"
import EffectPartition from "@/examples/effect-partition"
import EffectPromise from "@/examples/effect-promise"
import EffectRace from "@/examples/effect-race"
import EffectRaceAll from "@/examples/effect-raceall"
import EffectRepeatSpaced from "@/examples/effect-repeat-spaced"
import EffectRepeatWhileOutput from "@/examples/effect-repeat-while-output"
import EffectRetryExponential from "@/examples/effect-retry-exponential"
import EffectRetryRecurs from "@/examples/effect-retry-recurs"
import EffectSleep from "@/examples/effect-sleep"
import EffectSucceed from "@/examples/effect-succeed"
import EffectSync from "@/examples/effect-sync"
import EffectTimeout from "@/examples/effect-timeout"
import EffectValidate from "@/examples/effect-validate"

import RefMake from "@/examples/ref-make"
import RefUpdateAndGet from "@/examples/ref-update-and-get"
import type { ExampleMeta } from "@/lib/example-types"

type ExampleComponent = React.ComponentType<{
  index: number
  metadata: ExampleMeta
  exampleId: string
}>

const exampleComponentById: Record<string, ExampleComponent> = {
  "effect-acquire-release": EffectAcquireRelease,
  "effect-add-finalizer": EffectAddFinalizer,
  "effect-all-short-circuit": EffectAllShortCircuit,
  "effect-all": EffectAll,
  "effect-die": EffectDie,
  "effect-eventually": EffectEventually,
  "effect-firstsuccessof": EffectFirstSuccessOf,
  "effect-fail": EffectFail,
  "effect-foreach": EffectForEach,
  "effect-orelse": EffectOrElse,
  "effect-partition": EffectPartition,
  "effect-promise": EffectPromise,
  "effect-race": EffectRace,
  "effect-raceall": EffectRaceAll,
  "effect-repeat-spaced": EffectRepeatSpaced,
  "effect-repeat-while-output": EffectRepeatWhileOutput,
  "effect-retry-exponential": EffectRetryExponential,
  "effect-retry-recurs": EffectRetryRecurs,
  "effect-sleep": EffectSleep,
  "effect-succeed": EffectSucceed,
  "effect-sync": EffectSync,
  "effect-timeout": EffectTimeout,
  "effect-validate": EffectValidate,
  "ref-make": RefMake,
  "ref-update-and-get": RefUpdateAndGet,
}

import { defaultSpring } from "@/animations"
import { EffectLogo } from "@/components/feedback"
import { NavigationSidebar } from "@/components/layout/NavigationSidebar"
import { PageHeader } from "@/components/layout/PageHeader"
import { QuickOpen } from "@/components/ui"
import type { AppItem } from "@/lib/example-types"
import { appItems, createExampleId } from "@/shared/appItems"
import { taskSounds } from "@/sounds/TaskSounds"

// Helper function to get item section
function getItemSection(item: AppItem): string {
  return item.metadata.section
}

function AppContentInner() {
  const [isMuted, setIsMuted] = useState(false)

  // Update sound system when mute changes
  useEffect(() => {
    taskSounds.setMuted(isMuted)
  }, [isMuted])

  const [currentExampleId, setCurrentExampleId] = useState<string | undefined>()

  // Handle example selection from sidebar
  const handleExampleSelect = useCallback((id: string) => {
    setCurrentExampleId(id)
    // Scroll to the element
    const element = document.getElementById(id)
    if (element) {
      element.scrollIntoView({ behavior: "smooth", block: "center" })
    }
  }, [])

  // Prepare example metadata once for both navigation and quick-open
  const exampleDisplayItems = useMemo(
    () =>
      appItems.map(item => ({
        id: createExampleId(item.metadata.name, item.metadata.variant),
        name: item.metadata.name,
        ...(item.metadata.variant ? { variant: item.metadata.variant } : {}),
        section: item.metadata.section,
      })),
    [],
  )

  const exampleIdSet = useMemo(
    () => new Set(exampleDisplayItems.map(example => example.id)),
    [exampleDisplayItems],
  )

  const pathname = usePathname()

  useEffect(() => {
    if (!pathname) return
    const segments = pathname.split("/").filter(Boolean)
    const rawTarget = segments.at(-1)

    if (!rawTarget) {
      setCurrentExampleId(undefined)
      return
    }

    const targetId = decodeURIComponent(rawTarget)
    if (!exampleIdSet.has(targetId)) return

    window.requestAnimationFrame(() => {
      // RequestAnimationFrame ensures the DOM is ready before attempting to scroll.
      handleExampleSelect(targetId)
    })
  }, [pathname, exampleIdSet, handleExampleSelect])

  return (
    <div className="min-h-screen font-mono relative overflow-hidden">
      {/* Command-K quick-open modal */}
      <QuickOpen items={exampleDisplayItems} onSelect={handleExampleSelect} />

      <div className="max-w-screen-l mx-auto relative">
        {/* Navigation Sidebar */}
        <NavigationSidebar
          examples={exampleDisplayItems}
          currentExample={currentExampleId || undefined}
          onExampleSelect={handleExampleSelect}
        />

        <div className="xl:ml-64 p-4 sm:p-8">
          <div className="w-full flex flex-col items-center max-w-screen-md mx-auto relative z-10">
            <PageHeader isMuted={isMuted} onMuteToggle={() => setIsMuted(!isMuted)} />

            {/* Introduction section */}
            <div className="w-full max-w-screen-md mt-24 mb-12 p-8 border border-neutral-700/50 rounded-2xl shadow-2xl bg-gradient-to-br from-neutral-900/80 to-neutral-900/40 backdrop-blur-sm font-mono text-neutral-300 text-lg relative overflow-hidden">
              <div className="relative z-10">
                <p className="leading-relaxed text-left text-xl font-light">
                  Here are some interactive examples of TypeScript's beautiful{" "}
                  <EffectLogo className="inline-block  h-4 relative top-[-1px] pr-3 opacity-90" />
                  <a
                    href="https://effect.website"
                    target="_blank"
                    rel="noopener noreferrer"
                    className="font-extrabold cursor-pointer hover:text-white transition-all duration-300 tracking-wider  inline-block"
                  >
                    Effect
                  </a>{" "}
                  library. Tap the following effects to{" "}
                  <PlayIcon size={16} className="inline mr-2" weight="bold" />
                  <span className="font-bold ">run</span>,{" "}
                  <StopIcon size={16} className="inline mr-2" weight="bold" />
                  <span className="font-bold ">interrupt</span>, or{" "}
                  <ArrowClockwiseIcon size={16} className="inline mr-2" weight="bold" />
                  <span className="font-bold ">reset</span> them.
                </p>
              </div>
            </div>

            {/* Multiple effect examples and callouts */}
            <div className="w-full max-w-screen-md flex flex-col items-center gap-y-8 sm:gap-y-12">
              {appItems.map((item, index) => {
                const prevItem: AppItem | undefined = index > 0 ? appItems[index - 1] : undefined
                const showSectionHeader =
                  index === 0 ||
                  (prevItem !== undefined && getItemSection(prevItem) !== getItemSection(item))

                return (
                  <Fragment key={index}>
                    {showSectionHeader && (
                      <div className="w-full mt-16 mb-0">
                        <h2 className="text-lg sm:text-2xl font-mono text-neutral-300 tracking-wider font-bold relative">
                          <div className="flex items-center gap-3">
                            <div className="p-2 rounded-lg bg-gradient-to-br from-neutral-800 to-neutral-900 border border-neutral-700/50  ">
                              <HashStraightIcon
                                weight="bold"
                                size={20}
                                className="text-neutral-400"
                              />
                            </div>
                            <span className="text-neutral-400">
                              {getItemSection(item).toUpperCase()}
                            </span>
                          </div>
                        </h2>
                      </div>
                    )}
                    <div
                      className="w-full relative"
                      id={createExampleId(item.metadata.name, item.metadata.variant)}
                    >
                      {(() => {
                        const Component = exampleComponentById[item.metadata.id]
                        if (!Component) return null
                        return (
                          <Component
                            metadata={item.metadata}
                            index={index}
                            exampleId={createExampleId(item.metadata.name, item.metadata.variant)}
                          />
                        )
                      })()}
                    </div>
                  </Fragment>
                )
              })}
            </div>

            {/* Footer */}
            <footer className="w-full max-w-screen-md mt-40 mb-12 flex items-center justify-between text-xs sm:text-base">
              {/* Left side */}
              <div className="text-neutral-400 font-bold tracking-wide flex items-center gap-1.5 sm:gap-2">
                EFFECT OR
                <SkullIcon size={16} weight="fill" className="text-neutral-400 sm:hidden" />
                <SkullIcon
                  size={19}
                  weight="fill"
                  className="text-neutral-400 hidden sm:block ml-[3px]"
                />
              </div>

              {/* Right side */}
              <a
                href="https://twitter.com/kitlangton"
                target="_blank"
                rel="noopener noreferrer"
                className="text-neutral-400 hover:text-neutral-200 transition-all duration-300 font-bold tracking-wide group flex items-center gap-1.5 sm:gap-2"
              >
                <HeartIcon
                  size={14}
                  weight="fill"
                  className="text-red-500 group-hover:text-red-400 transition-transform duration-300 group-hover:scale-110 sm:hidden"
                />
                <HeartIcon
                  size={18}
                  weight="fill"
                  className="text-red-500 group-hover:text-red-400 transition-transform duration-300 group-hover:scale-110 hidden sm:block"
                />
                KIT
              </a>
            </footer>
          </div>
        </div>
      </div>
    </div>
  )
}

export function AppContent() {
  return (
    <MotionConfig transition={defaultSpring}>
      <AppContentInner />
    </MotionConfig>
  )
}


================================================
FILE: src/VisualEffect.test.ts
================================================
import { Effect } from "effect"
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
import { VisualEffect } from "./VisualEffect"

describe("VisualEffect State Transitions", () => {
  let task: VisualEffect<string, string>
  let stateChanges: Array<{ from: string; to: string; timestamp: number }> = []

  beforeEach(() => {
    stateChanges = []
    // Don't use fake timers by default - many tests need real async behavior
  })

  afterEach(() => {
    vi.useRealTimers()
    task?.reset()
  })

  const createTask = (effect: Effect.Effect<string, string>) => {
    const visualTask = new VisualEffect("test", effect)

    // Track all state changes
    visualTask.subscribe(() => {
      const currentState = visualTask.state
      const timestamp = Date.now()

      if (stateChanges.length > 0) {
        const lastState = stateChanges[stateChanges.length - 1]
        stateChanges.push({
          from: lastState?.to ?? "initial",
          to: currentState.type,
          timestamp,
        })
      } else {
        stateChanges.push({
          from: "initial",
          to: currentState.type,
          timestamp,
        })
      }
    })

    return visualTask
  }

  describe("Basic State Transitions", () => {
    it("should start in idle state", () => {
      task = createTask(Effect.succeed("test"))
      expect(task.state.type).toBe("idle")
    })

    it("should transition idle -> running -> completed for successful effect", async () => {
      // Use an effect that takes time so we can observe the running state
      task = createTask(Effect.sleep(50).pipe(Effect.map(() => "success")))

      const promise = Effect.runPromise(task.effect)

      // Should be running now
      expect(task.state.type).toBe("running")

      const result = await promise
      expect(result).toBe("success")
      expect(task.state.type).toBe("completed")

      if (task.state.type === "completed") {
        expect(task.state.result).toBe("success")
      }
    })

    it("should transition idle -> running -> failed for failing effect", async () => {
      // Use an effect that takes time before failing
      task = createTask(Effect.sleep(50).pipe(Effect.flatMap(() => Effect.fail("error"))))

      try {
        await Effect.runPromise(task.effect)
      } catch (error: unknown) {
        if (error instanceof Error) {
          expect(error.message).toBe("error")
        } else {
          throw error
        }
      }

      expect(task.state.type).toBe("failed")
      if (task.state.type === "failed") {
        expect(task.state.error).toBe("error")
      }
    })

    it("should transition to interrupted when effect is interrupted", async () => {
      task = createTask(Effect.sleep(1000).pipe(Effect.map(() => "never")))

      const runPromise = task.run()
      expect(task.state.type).toBe("running")

      // Interrupt the task
      task.interrupt()

      try {
        await runPromise
      } catch {
        // Expected to throw
      }

      expect(task.state.type).toBe("interrupted")
    })
  })

  describe("Reset Behavior", () => {
    it("should reset from completed to idle", async () => {
      task = createTask(Effect.succeed("done"))

      await Effect.runPromise(task.effect)
      expect(task.state.type).toBe("completed")

      task.reset()
      expect(task.state.type).toBe("idle")
    })

    it("should reset from failed to idle", async () => {
      task = createTask(Effect.fail("error"))

      try {
        await Effect.runPromise(task.effect)
      } catch {
        // Expected to throw
      }

      expect(task.state.type).toBe("failed")

      task.reset()
      expect(task.state.type).toBe("idle")
    })

    it("should reset from running to idle (interrupt case)", async () => {
      task = createTask(Effect.sleep(1000).pipe(Effect.map(() => "slow")))

      const promise = Effect.runPromise(task.effect)
      expect(task.state.type).toBe("running")

      task.reset()
      expect(task.state.type).toBe("idle")

      try {
        await promise
      } catch {
        // Expected to throw due to interruption
      }

      // Should stay idle after interruption completes
      expect(task.state.type).toBe("idle")
    })

    it("should prevent interrupted state during reset", async () => {
      task = createTask(Effect.sleep(1000).pipe(Effect.map(() => "slow")))

      Effect.runPromise(task.effect)
      expect(task.state.type).toBe("running")

      // Reset should prevent "interrupted" state
      task.reset()

      // Even if async interruption happens, should stay idle
      await vi.waitFor(() => {
        expect(task.state.type).toBe("idle")
      })

      // Verify no interrupted state in history
      const hasInterrupted = stateChanges.some(change => change.to === "interrupted")
      expect(hasInterrupted).toBe(false)
    })
  })

  describe("Effect Caching", () => {
    it("should return cached result for completed effects", async () => {
      let executionCount = 0
      const countingEffect = Effect.sync(() => {
        executionCount++
        return `execution-${executionCount}`
      })

      task = createTask(countingEffect)

      // First execution
      const result1 = await Effect.runPromise(task.effect)
      expect(result1).toBe("execution-1")
      expect(executionCount).toBe(1)

      // Second call should return cached result
      const result2 = await Effect.runPromise(task.effect)
      expect(result2).toBe("execution-1") // Same result
      expect(executionCount).toBe(1) // No additional execution
    })

    it("should re-execute after reset", async () => {
      let executionCount = 0
      const countingEffect = Effect.sync(() => {
        executionCount++
        return `execution-${executionCount}`
      })

      task = createTask(countingEffect)

      // First execution
      await Effect.runPromise(task.effect)
      expect(executionCount).toBe(1)

      // Reset and execute again
      task.reset()
      const result2 = await Effect.runPromise(task.effect)
      expect(result2).toBe("execution-2")
      expect(executionCount).toBe(2)
    })
  })

  describe("Parent-Child Relationships", () => {
    it("should register child effects and reset them when parent is reset", async () => {
      const childStates: Array<string> = []

      // Create a child effect that tracks its state changes
      const childEffect = Effect.gen(function* () {
        const child = createTask(Effect.sleep(1000).pipe(Effect.map(() => "child-done")))

        // Track child state changes
        child.subscribe(() => {
          childStates.push(child.state.type)
        })

        // Run the child as part of the parent's effect
        // This will make it register with the parent via VisualEffectService
        const childResult = yield* child.effect

        return childResult
      })

      const parent = createTask(
        Effect.gen(function* () {
          console.log("Parent starting")
          const result = yield* childEffect
          console.log("Parent finishing")
          return result
        }),
      )

      // Start the parent
      const parentPromise = parent.run()
      expect(parent.state.type).toBe("running")

      // Wait for the child to enter running state
      await vi.waitFor(() => {
        expect(childStates).toContain("running")
      })

      // Reset the parent - this should cascade to children
      parent.reset()
      expect(parent.state.type).toBe("idle")

      try {
        await parentPromise
      } catch {
        // Expected to throw due to interruption
      }

      // Child should have been reset too (transitioned to idle)
      expect(childStates).toContain("running")
      expect(childStates).toContain("idle")

      // Verify child ended up in idle state
      const finalChildState = childStates[childStates.length - 1]
      expect(finalChildState).toBe("idle")
    })

    it("should interrupt child effects when parent is interrupted", async () => {
      const childStates: Array<string> = []

      // Create a child effect that tracks its state changes
      const childEffect = Effect.gen(function* () {
        const child = createTask(Effect.sleep(1000).pipe(Effect.map(() => "child-done")))

        // Track child state changes
        child.subscribe(() => {
          childStates.push(child.state.type)
        })

        // Run the child as part of the parent's effect
        // This ensures it gets registered with the parent
        return yield* child.effect
      })

      const parent = createTask(childEffect)

      // Start the parent
      const parentPromise = parent.run()
      expect(parent.state.type).toBe("running")

      // Wait for child running
      await vi.waitFor(() => {
        expect(childStates).toContain("running")
      })

      // Interrupt the parent - this should cascade to children
      parent.interrupt()
      expect(parent.state.type).toBe("interrupted")

      try {
        await parentPromise
      } catch {
        // Expected to throw due to interruption
      }

      // Child should have been interrupted too
      expect(childStates).toContain("running")
      expect(childStates).toContain("interrupted")
    })
  })
})


================================================
FILE: src/VisualEffect.ts
================================================
"use client"

import { Context, Effect, Fiber, Option } from "effect"
import { useSyncExternalStore } from "react"
import { taskSounds } from "./sounds/TaskSounds"

export type EffectState<A, E> =
  | { type: "idle" }
  | { type: "running" }
  | { type: "completed"; result: A }
  | { type: "failed"; error: E }
  | { type: "interrupted" }
  | { type: "death"; error: unknown }

// Pattern matching helper for EffectState (internal use only)
const matchEffectState = <A, E, T>(
  state: EffectState<A, E>,
  cases: {
    idle: () => T
    running: () => T
    completed: (result: A) => T
    failed: (error: E) => T
    interrupted: () => T
    death: (error: unknown) => T
  },
): T => {
  switch (state.type) {
    case "idle":
      return cases.idle()
    case "running":
      return cases.running()
    case "completed":
      return cases.completed(state.result)
    case "failed":
      return cases.failed(state.error)
    case "interrupted":
      return cases.interrupted()
    case "death":
      return cases.death(state.error)
  }
}

export interface Notification {
  id: string
  message: string
  timestamp: number
  duration?: number // auto-dismiss after this many ms
  icon?: string // emoji or icon
}

// Valid state transitions for the state machine
const VALID_TRANSITIONS: Record<string, Set<string>> = {
  idle: new Set(["running", "idle"]),
  running: new Set(["completed", "failed", "interrupted", "death", "idle", "running"]),
  completed: new Set(["idle", "running", "completed"]),
  failed: new Set(["failed", "idle", "running"]),
  interrupted: new Set(["interrupted", "idle", "running"]),
  death: new Set(["death", "idle", "running"]),
}

// Service interface for parent-child VisualEffect communication
export interface VisualEffectService {
  readonly addChild: (child: VisualEffect<unknown, unknown>) => Effect.Effect<void>
  readonly notify: (
    message: string,
    options?: { duration?: number; icon?: string },
  ) => Effect.Effect<void>
}

const VisualEffectService = Context.GenericTag<VisualEffectService>("VisualEffectService")

// Service implementation
class VisualEffectServiceImpl implements VisualEffectService {
  constructor(private parent: VisualEffect<unknown, unknown>) {}

  addChild = (child: VisualEffect<unknown, unknown>): Effect.Effect<void> =>
    Effect.sync(() => {
      this.parent.addChildEffect(child)
    })

  notify = (message: string, options?: { duration?: number; icon?: string }): Effect.Effect<void> =>
    Effect.sync(() => {
      this.parent.notify(message, options)
    })
}

export class VisualEffect<A, E = never> {
  private listeners = new Set<() => void>()
  private notificationListeners = new Set<() => void>()
  private currentNotification: Notification | null = null
  private fiber: Fiber.RuntimeFiber<A, E> | null = null
  private timeouts = new Set<ReturnType<typeof setTimeout>>()
  private isResetting = false
  private children = new Set<VisualEffect<unknown, unknown>>()

  state: EffectState<A, E> = { type: "idle" }

  addChildEffect(child: VisualEffect<unknown, unknown>): void {
    this.children.add(child)
  }

  startTime: number | null = null
  endTime: number | null = null

  constructor(
    public name: string,
    private _effect: Effect.Effect<A, E>,
    public showTimer: boolean = false,
  ) {}

  // The effect property returns an Effect that updates this effect's state when run
  get effect(): Effect.Effect<A, E> {
    // Quick return for terminal states
    const quickReturn = matchEffectState(this.state, {
      idle: () => null,
      running: () => null,
      completed: result => Effect.succeed(result),
      failed: error => Effect.fail(error) as Effect.Effect<A, E>,
      interrupted: () => null,
      death: error => Effect.die(error),
    })

    if (quickReturn) return quickReturn

    // Create the effect
    return Effect.gen(
      function* (this: VisualEffect<A, E>) {
        // Register with parent service if available
        const maybeParentService = yield* Effect.serviceOption(VisualEffectService)
        yield* Option.match(maybeParentService, {
          onNone: () => Effect.void,
          onSome: service => service.addChild(this),
        })

        // Mark as running
        this.setState({ type: "running" })

        // Execute the wrapped effect with appropriate service provided to all nested effects
        const effectWithRootService = this._effect.pipe(
          Effect.provideService(VisualEffectService, new VisualEffectServiceImpl(this)),
        )

        const wrappedEffect = Option.isSome(maybeParentService)
          ? this._effect
          : effectWithRootService

        return yield* wrappedEffect
      }.bind(this),
    ).pipe(
      // Clear notifications on any non-success exit
      Effect.tapErrorCause(() => Effect.sync(() => this.clearNotifications())),
      // Handle success
      Effect.tap(result =>
        Effect.sync(() => {
          this.setState({ type: "completed", result })
        }),
      ),
      // Handle errors
      Effect.tapError((error: E) =>
        Effect.sync(() => {
          this.setState({ type: "failed", error })
        }),
      ),
      // Handle interruption
      Effect.onInterrupt(() =>
        Effect.sync(() => {
          if (!this.isResetting) {
            this.setState({ type: "interrupted" })
          }
        }),
      ),
      // Handle defects
      Effect.tapDefect((defect: unknown) =>
        Effect.sync(() => {
          if (process.env.NODE_ENV === "development") {
            console.error(`Effect "${this.name}" died with defect:`, defect)
          }
          this.setState({ type: "death", error: defect })
        }),
      ),
    ) as Effect.Effect<A, E>
  }

  // Observable pattern methods
  subscribe(listener: () => void) {
    this.listeners.add(listener)
    return () => {
      this.listeners.delete(listener)
    }
  }

  subscribeToNotifications(listener: () => void) {
    this.notificationListeners.add(listener)
    return () => {
      this.notificationListeners.delete(listener)
    }
  }

  notify(message: string, options?: { duration?: number; icon?: string }): void {
    // Clear any existing notification and its timeout
    this.clearNotifications()

    const notification: Notification = {
      id: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
      message,
      timestamp: Date.now(),
      duration: options?.duration ?? 2000, // default 2 seconds
      ...(options?.icon && { icon: options.icon }),
    }

    this.currentNotification = notification
    this.notifyNotificationListeners()

    // Auto-remove after duration
    if (notification.duration) {
      const timeoutId = setTimeout(() => {
        this.clearNotifications()
      }, notification.duration)
      this.timeouts.add(timeoutId)
    }
  }

  getCurrentNotification(): Notification | null {
    return this.currentNotification
  }

  private clearNotifications(): void {
    this.currentNotification = null
    this.clearTimeouts()
    this.notifyNotificationListeners()
  }

  private notifyStateListeners() {
    this.listeners.forEach(listener => {
      listener()
    })
  }

  private notifyNotificationListeners() {
    this.notificationListeners.forEach(listener => {
      listener()
    })
  }

  private setState(newState: EffectState<A, E>) {
    if (this.isResetting) return

    const previousState = this.state
    const validTransitions = VALID_TRANSITIONS[previousState.type]

    if (!validTransitions?.has(newState.type)) {
      if (process.env.NODE_ENV === "development") {
        console.warn(
          `Invalid state transition from ${previousState.type} to ${newState.type} for task ${this.name}`,
        )
      }
      return
    }

    this.state = newState

    // Track timing
    if (this.showTimer) {
      if (newState.type === "running" && previousState.type !== "running") {
        this.startTime = Date.now()
        this.endTime = null
      } else if (previousState.type === "running" && newState.type !== "running") {
        this.endTime = Date.now()
      }
    }

    // Trigger sounds
    if (previousState.type !== newState.type) {
      matchEffectState(newState, {
        idle: () => {},
        running: () => taskSounds.playRunning().catch(() => {}),
        completed: () => taskSounds.playSuccess().catch(() => {}),
        failed: () => taskSounds.playFailure().catch(() => {}),
        interrupted: () => taskSounds.playInterrupted().catch(() => {}),
        death: () => taskSounds.playDeath().catch(() => {}),
      })
    }

    this.notifyStateListeners()
  }

  private clearTimeouts() {
    this.timeouts.forEach(clearTimeout)
    this.timeouts.clear()
  }

  reset() {
    this.isResetting = true
    try {
      // Reset all children first so their state transitions obey the reset flag
      this.children.forEach(child => {
        child.reset()
      })

      // Clear the children collection since they're no longer relevant
      this.children.clear()

      // Interrupt our own fiber if it's still running
      if (this.fiber) {
        Effect.runFork(Fiber.interrupt(this.fiber))
        this.fiber = null
      }

      // Clean up any scheduled work / caches
      this.clearTimeouts()
      this.clearNotifications() // Clear notifications on reset
      this.startTime = null
      this.endTime = null
    } finally {
      // Allow subsequent state transitions
      this.isResetting = false
    }

    // Now that the reset flag is cleared, transition ourselves to idle
    this.setState({ type: "idle" })
  }

  async run() {
    try {
      this.fiber = Effect.runFork(this.effect)
      await Effect.runPromise(Fiber.await(this.fiber))
    } catch {
      // Error handling is done within the effect
    } finally {
      this.fiber = null
    }
  }

  interrupt() {
    if (this.state.type === "running") {
      const fiberToInterrupt = this.fiber
      this.fiber = null

      // Optimistically mark as interrupted so observers update immediately.
      // The onInterrupt handler inside the effect will confirm this later.
      this.setState({ type: "interrupted" })

      if (fiberToInterrupt) {
        Effect.runFork(Fiber.interrupt(fiberToInterrupt))
      }
    }
  }
}

// Utility function for effects to notify their parent
export const notify = (
  message: string,
  options?: { duration?: number; icon?: string },
): Effect.Effect<void, never> =>
  Effect.serviceOption(VisualEffectService).pipe(
    Effect.flatMap(option =>
      Option.isSome(option) ? option.value.notify(message, options) : Effect.void,
    ),
  )

// Granular React hooks for better performance

// Subscribe only to state changes
export function useVisualEffectState<A, E>(effect: VisualEffect<A, E>) {
  return useSyncExternalStore(effect.subscribe.bind(effect), () => effect.state)
}

// Subscribe only to notification changes
export function useVisualEffectNotification<A, E>(effect: VisualEffect<A, E>) {
  return useSyncExternalStore(effect.subscribeToNotifications.bind(effect), () =>
    effect.getCurrentNotification(),
  )
}

// Subscribe for re-renders only (no return value)
export function useVisualEffectSubscription<A, E>(effect: VisualEffect<A, E>) {
  useSyncExternalStore(effect.subscribe.bind(effect), () => effect.state)
}

// Factory function - compatible with V1 signature
export const visualEffect = <A, E = never>(
  name: string,
  effect: Effect.Effect<A, E>,
  showTimer: boolean = false,
) => new VisualEffect(name, effect, showTimer)


================================================
FILE: src/VisualRef.ts
================================================
"use client"

import { Effect, Ref } from "effect"
import { useMemo, useSyncExternalStore } from "react"
import { taskSounds } from "./sounds/TaskSounds"

export class VisualRef<A> {
  private listeners = new Set<() => void>()
  private _ref: Ref.Ref<A> | null = null
  private _currentValue: A
  private _justChanged = false
  private animationTimeoutId: ReturnType<typeof setTimeout> | null = null

  constructor(
    public name: string,
    private initialValue: A,
  ) {
    this._currentValue = initialValue
  }

  get ref(): Effect.Effect<Ref.Ref<A>, never> {
    if (this._ref) return Effect.succeed(this._ref)

    return Ref.make(this.initialValue).pipe(
      Effect.tap(ref =>
        Effect.sync(() => {
          this._ref = ref
        }),
      ),
    )
  }

  get value(): A {
    return this._currentValue
  }

  get justChanged(): boolean {
    return this._justChanged
  }

  updateValue(newValue: A): void {
    if (this._currentValue === newValue) return

    // Play sound
    taskSounds.playRefUpdate().catch(() => {})

    // Update state
    this._currentValue = newValue
    this._justChanged = true

    // Clear existing timeout
    if (this.animationTimeoutId) {
      clearTimeout(this.animationTimeoutId)
    }

    // Schedule animation cleanup
    this.animationTimeoutId = setTimeout(() => {
      this._justChanged = false
      this.notify()
      this.animationTimeoutId = null
    }, 50)

    this.notify()
  }

  updateAndGet(updateFn: (current: A) => A): Effect.Effect<A> {
    return this.ref.pipe(
      Effect.flatMap(ref => Ref.updateAndGet(ref, updateFn)),
      Effect.tap(newValue => Effect.sync(() => this.updateValue(newValue))),
    )
  }

  get(): Effect.Effect<A> {
    return this.ref.pipe(Effect.flatMap(Ref.get))
  }

  subscribe(listener: () => void) {
    this.listeners.add(listener)
    return () => this.listeners.delete(listener)
  }

  private notify() {
    for (const listener of this.listeners) {
      listener()
    }
  }

  reset() {
    // Clear animation timeout
    if (this.animationTimeoutId) {
      clearTimeout(this.animationTimeoutId)
      this.animationTimeoutId = null
    }

    // Reset state
    this._currentValue = this.initialValue
    this._justChanged = false
    this._ref = null

    this.notify()
  }
}

export const visualRef = <A>(name: string, initialValue: A) => new VisualRef(name, initialValue)

export function useVisualRef<A>(ref: VisualRef<A>) {
  const subscribe = useMemo(() => (listener: () => void) => ref.subscribe(listener), [ref])

  const getSnapshot = useMemo(() => {
    // Cache to avoid creating new objects on every read
    let cache = {
      value: ref.value,
      justChanged: ref.justChanged,
    }

    return () => {
      const currentValue = ref.value
      const currentFlag = ref.justChanged

      if (cache.value !== currentValue || cache.justChanged !== currentFlag) {
        cache = { value: currentValue, justChanged: currentFlag }
      }

      return cache
    }
  }, [ref])

  const snapshot = useSyncExternalStore(subscribe, getSnapshot)

  return {
    ref,
    value: snapshot.value,
    justChanged: snapshot.justChanged,
  } as const
}


================================================
FILE: src/VisualScope.ts
================================================
import { taskSounds } from "./sounds/TaskSounds"

export type ScopeState = "idle" | "acquiring" | "active" | "releasing" | "released"

export type FinalizerState = "pending" | "running" | "completed"

export interface Finalizer {
  id: string
  name: string
  timestamp: number
  state: FinalizerState
}

export class VisualScope {
  id: string
  state: ScopeState = "idle"
  finalizers: Array<Finalizer> = []
  private subscribers: Set<() => void> = new Set()

  constructor(id: string) {
    this.id = id
  }

  subscribe(callback: () => void): () => void {
    this.subscribers.add(callback)
    return () => this.subscribers.delete(callback)
  }

  private notify() {
    this.subscribers.forEach(callback => {
      callback()
    })
  }

  setState(newState: ScopeState) {
    if (this.state === newState) return

    this.state = newState
    this.notify()
  }

  addFinalizer(name: string): string {
    const id = `finalizer-${name}`
    const finalizer: Finalizer = {
      id,
      name,
      timestamp: Date.now(),
      state: "pending",
    }

    this.finalizers.push(finalizer)
    taskSounds.playFinalizerCreated()

    this.notify()
    return id
  }

  async runFinalizers() {
    this.setState("releasing")

    // Run finalizers in reverse order (LIFO)
    const finalizersToRun = [...this.finalizers].reverse()

    for (const finalizer of finalizersToRun) {
      // Abort if scope was reset while releasing
      if (this.state !== "releasing") {
        return
      }

      finalizer.state = "running"
      taskSounds.playFinalizerRunning()
      this.notify()

      // Simulate finalizer execution
      await new Promise(resolve => setTimeout(resolve, 800))

      // If scope was reset during the simulated execution, abort further processing
      if (this.state !== "releasing") {
        return
      }

      finalizer.state = "completed"
      taskSounds.playFinalizerCompleted()
      this.notify()
    }

    // Only mark as released if we weren't reset in the meantime
    if (this.state === "releasing") {
      this.setState("released")
    }
  }

  reset() {
    this.state = "idle"
    this.finalizers = []
    this.notify()
  }
}


================================================
FILE: src/animations.ts
================================================
// Animation configuration for consistent motion design across components

// Default spring for MotionConfig wrapper
export const defaultSpring = {
  type: "spring" as const,
  // Critical damping occurs when damping ≈ 2 * sqrt(stiffness * mass).
  // With mass = 1, choose stiffness that feels snappy but natural and
  // compute damping accordingly.
  mass: 1,
  stiffness: 200,
  damping: 2 * Math.sqrt(200), // ≈ 28.28
  // No bounce/overshoot for critically damped motion
  bounce: 0,
}

// Spring presets
export const springs = {
  // Main spring for general animations
  default: {
    type: "spring" as const,
    stiffness: 180,
    damping: 25,
    mass: 0.8,
  },
  // Bouncy spring for completion animations
  bouncy: {
    type: "spring" as const,
    bounce: 0.3,
    visualDuration: 0.5,
  },
  // Node width animation with custom bounce
  nodeWidth: {
    type: "spring" as const,
    stiffness: 180,
    damping: 25,
    mass: 0.8,
    visualDuration: 0.6,
    bounce: 0.3,
  },
  // Content scale animation for completion
  contentScale: {
    type: "spring" as const,
    bounce: 0.3,
    visualDuration: 0.5,
    stiffness: 260,
    damping: 18,
  },
  // Failure bubble animation
  failureBubble: {
    type: "spring" as const,
    visualDuration: 0.2,
    delay: 0.05,
    bounce: 0.3,
  },
}

// Shake animation constants
export const shake = {
  // Running state jitter
  running: {
    angleRange: 4,
    angleBase: 0.5,
    offsetRange: 1.5,
    offsetBase: 0.5,
    offsetYRange: 0.6,
    offsetYBase: 0.1,
    durationMin: 0.1,
    durationMax: 0.2,
  },
  // Failure/death shake - more intense
  failure: {
    intensity: 8,
    duration: 0.08,
    count: 6,
    rotationRange: 8,
    returnDuration: 0.3,
  },
  // Failure bubble shake - gentler
  bubble: {
    intensity: 4,
    duration: 0.08,
    count: 4,
    rotationRange: 4,
    yOffset: -5,
    returnDuration: 0.3,
    delay: 100,
  },
}

// Animation durations and timing
export const timing = {
  // Border pulsing
  borderPulse: {
    duration: 1.5,
    values: [1, 0.3, 1],
  },
  // Glow pulsing
  glowPulse: {
    duration: 0.5,
    values: [1, 5, 1],
  },
  // Flash animation
  flash: {
    duration: 1.0,
    ease: "linear" as const,
  },
  // Smooth exit animations
  exit: {
    duration: 0.3,
    ease: [0.4, 0, 0.6, 1] as const,
  },
  // Glitch effect timing
  glitch: {
    initialCount: 3,
    initialDelayMin: 20,
    initialDelayMax: 70,
    pauseMin: 50,
    pauseMax: 150,
    subtleDelayMin: 300,
    subtleDelayMax: 800,
  },
}

// Color values for animations
export const colors = {
  // Flash colors
  flash: "rgba(255, 255, 255, 0.8)",

  // Border colors
  border: {
    default: "rgba(255, 255, 255, 0.1)",
    death: "rgba(220, 38, 38, 0.4)",
  },

  // Failure bubble colors
  failureBubble: {
    background: "rgba(239, 68, 68, 0.95)",
    text: "text-red-50",
    shadow: "0 0px 16px rgba(0, 0, 0, 0.5)",
  },

  // Glow effects
  glow: {
    death: "rgba(220, 38, 38, 0.8)",
    running: "rgba(100, 200, 255, 0.2)",
  },
}

// Transform and filter values
export const effects = {
  // Death filter effects
  death: {
    contrast: 1.2,
    brightness: 0.8,
  },
  // Glitch intensity ranges
  glitch: {
    scaleRange: 0.2,
    glowMin: 3,
    glowMax: 7,
    intensePulseMax: 10,
  },
}


================================================
FILE: src/components/CodeBlock.tsx
================================================
import type { MotionStyle } from "motion/react"
import { AnimatePresence, motion } from "motion/react"
import type { Language, RenderProps, Token } from "prism-react-renderer"
import { Highlight, themes } from "prism-react-renderer"
import type React from "react"
import { useEffect, useMemo, useRef } from "react"

const { oneDark } = themes

interface CodeBlockProps {
  code: string
  language?: Language
  /**
   * Line numbers (1-based) that should be visually highlighted.
   */
  activeLines?: Array<number>
  /**
   * Called when the user hovers a line. null means hover left the block.
   */
  onLineHover?: (lineNo: number | null) => void
  /**
   * Optional style overrides for the <pre> element.
   */
  style?: React.CSSProperties
}

export const CodeBlock: React.FC<CodeBlockProps> = ({
  activeLines = [],
  code,
  language = "typescript",
  onLineHover,
  style,
}) => {
  // Ensure active lines are unique for comparison in effect deps
  const active = Array.from(new Set(activeLines))

  // Store previous lines to compare for changes
  const prevLinesRef = useRef<Array<string>>([])
  const isInitialRender = useRef(true)

  // Split current code into lines for comparison
  const currentLines = useMemo(() => code.trim().split("\n"), [code])

  // Determine stable vs new/removed lines by content
  const lineStates = useMemo(() => {
    const prev = prevLinesRef.current
    const prevSet = new Set(prev)
    const currentSet = new Set(currentLines)

    // Don't animate on initial render
    if (isInitialRender.current) {
      prevLinesRef.current = [...currentLines]
      isInitialRender.current = false
      return { stable: currentSet, new: new Set(), removed: new Set() }
    }

    const stableLines = new Set()
    const newLines = new Set()
    const removedLines = new Set()

    // Find stable lines (exist in both)
    for (const line of currentLines) {
      if (prevSet.has(line)) {
        stableLines.add(line)
      } else {
        newLines.add(line)
      }
    }

    // Find removed lines (existed before but not now)
    for (const line of prev) {
      if (!currentSet.has(line)) {
        removedLines.add(line)
      }
    }

    // Update the ref for next comparison
    prevLinesRef.current = [...currentLines]

    return { stable: stableLines, new: newLines, removed: removedLines }
  }, [currentLines])

  useEffect(() => {
    if (active.length === 0) return
    // Scroll the first active line into view smoothly
    const selector = `[data-line-no="${active[0]}"]`
    const el = document.querySelector(selector)
    el?.scrollIntoView({ behavior: "smooth", block: "center" })
  }, [active])

  return (
    <motion.div
      transition={{
        type: "spring",
        visualDuration: 0.1,
        bounce: 0,
      }}
      style={{ overflow: "hidden" }}
    >
      <Highlight theme={oneDark} code={code.trim()} language={language}>
        {(highlightProps: RenderProps) => {
          const {
            className,
            getLineProps,
            getTokenProps,
            style: defaultStyle,
            tokens,
          } = highlightProps

          return (
            <pre
              className={className}
              style={{
                ...defaultStyle,
                margin: 0,
                borderRadius: 0,
                padding: 0,
                fontFamily: "Consolas, Monaco, 'Courier New', monospace",
                lineHeight: 1.6,
                backgroundColor: "transparent",
                whiteSpace: "pre-wrap",
                wordBreak: "break-word",
                wordWrap: "break-word",
                maxWidth: "100%",
                width: "100%",
                ...style,
              }}
            >
              <AnimatePresence mode="popLayout" initial={false}>
                {tokens.map((lineTokens: Token[], lineIndex: number) => {
                  const lineNo = lineIndex + 1
                  const isActive = active.includes(lineNo)
                  const lineContent = currentLines[lineIndex] ?? ""
                  // const isNewLine = lineStates.new.has(lineContent);
                  const isNewLine = lineStates.new.has(lineContent)

                  const lineProps = getLineProps({
                    line: lineTokens,
                    key: lineIndex,
                  })
                  const lineStyle: React.CSSProperties = {
                    display: "block",
                    paddingLeft: 0,
                    overflow: "hidden",
                    ...(isActive ? { background: "rgba(56, 189, 248, 0.15)" } : {}),
                    ...(lineProps.style ?? {}),
                  }
                  const motionStyle = lineStyle as MotionStyle

                  return (
                    <motion.div
                      key={`${lineIndex}-${lineContent}`}
                      className={lineProps.className}
                      style={motionStyle}
                      data-line-no={lineNo}
                      initial={isNewLine ? { opacity: 0, filter: "blur(6px)", height: 0 } : false}
                      animate={{
                        opacity: 1,
                        filter: "blur(0px)",
                        height: "auto",
                      }}
                      exit={{ opacity: 0, filter: "blur(6px)", height: 0 }}
                      transition={{
                        type: "spring",
                        visualDuration: 0.1,
                        bounce: 0,
                      }}
                      onMouseEnter={() => onLineHover?.(lineNo)}
                      onMouseLeave={() => onLineHover?.(null)}
                    >
                      {lineTokens.map((token: Token, tokenIndex: number) => {
                        const tokenProps = getTokenProps({
                          token,
                          key: tokenIndex,
                        })
                        // React keys must be passed directly rather than via spread props
                        const { key: _ignored, ...restTokenProps } = tokenProps
                        return <span key={`${lineNo}-${tokenIndex}`} {...restTokenProps} />
                      })}
                    </motion.div>
                  )
                })}
              </AnimatePresence>
            </pre>
          )
        }}
      </Highlight>
    </motion.div>
  )
}


================================================
FILE: src/components/HeaderView.tsx
================================================
"use client"
import {
  ArrowCounterClockwiseIcon,
  CheckIcon,
  LinkIcon,
  PlayIcon,
  StarFourIcon,
  StopIcon,
} from "@phosphor-icons/react"
import { AnimatePresence, motion } from "motion/react"
import { memo, useCallback, useEffect, useState } from "react"
import { GLOW_COLORS, TASK_COLORS } from "@/constants/colors"
import { useOptionKey } from "@/hooks/useOptionKey"
import { taskSounds } from "@/sounds/TaskSounds"
import { useVisualEffectSubscription, type VisualEffect } from "@/VisualEffect"
import type { VisualRef } from "@/VisualRef"

interface HeaderViewProps<A, E> {
  effect: VisualEffect<A, E>
  name: string
  variant?: string
  description?: React.ReactNode
  refs?: Array<VisualRef<unknown>>
  exampleId: string
}

function HeaderViewComponent({
  description,
  exampleId,
  name,
  refs = [],
  effect: task,
  variant,
}: HeaderViewProps<unknown, unknown>) {
  const [isHovered, setIsHovered] = useState(false)
  const [isPressed, setIsPressed] = useState(false)
  const [showCheckmark, setShowCheckmark] = useState(false)
  const [hasPlayedHoverSound, setHasPlayedHoverSound] = useState(false)
  const isOptionPressed = useOptionKey()
  useVisualEffectSubscription(task)
  const { state } = task

  const isRunning = state.type === "running"
  const isCompleted = state.type === "completed"
  const isFailed = state.type === "failed"
  const isInterrupted = state.type === "interrupted"
  const isDeath = state.type === "death"
  const canReset = isCompleted || isFailed || isInterrupted || isDeath

  const runWithDependencies = useCallback(async () => {
    await task.run()
  }, [task])

  const resetWithDependencies = useCallback(() => {
    task.reset()
    // Also reset refs passed in
    refs.forEach(refItem => {
      refItem.reset()
    })
    // Play reset sound
    taskSounds.playReset()
  }, [task, refs])

  const handleAction = useCallback(() => {
    // If Option is pressed and we have an exampleId, copy link
    if (isOptionPressed && exampleId) {
      const url = `${window.location.origin}/${exampleId}`
      navigator.clipboard.writeText(url).then(() => {
        setShowCheckmark(true)
        // Play copy success sound
        taskSounds.playLinkCopied()
        // Hide checkmark after 1.5 seconds
        setTimeout(() => {
          setShowCheckmark(false)
        }, 1500)
      })
      return
    }

    const currentState = task.state
    const running = currentState.type === "running"
    const resettable =
      currentState.type === "completed" ||
      currentState.type === "failed" ||
      currentState.type === "interrupted" ||
      currentState.type === "death"

    if (running) {
      task.interrupt()
    } else if (resettable) {
      resetWithDependencies()
    } else {
      runWithDependencies()
    }
  }, [task, resetWithDependencies, runWithDependencies, isOptionPressed, exampleId])

  const getIcon = () => {
    // Show checkmark after copying
    if (showCheckmark) {
      return (
        <motion.div
          key="check"
          initial={{ scale: 0, rotate: -180, filter: "blur(10px)" }}
          animate={{ scale: 1, rotate: 0, filter: "blur(0px)" }}
          exit={{ scale: 0, rotate: 180, filter: "blur(10px)" }}
          transition={{ type: "spring", stiffness: 300, damping: 20 }}
        >
          <CheckIcon size={24} weight="bold" />
        </motion.div>
      )
    }

    // Show link icon when Option is pressed AND hovering
    if (isOptionPressed && isHovered && exampleId) {
      return (
        <motion.div
          key="link"
          initial={{ scale: 0, rotate: -180, filter: "blur(10px)" }}
          animate={{ scale: 1, rotate: 0, filter: "blur(0px)" }}
          exit={{ scale: 0, rotate: 180, filter: "blur(10px)" }}
          transition={{ type: "spring", stiffness: 300, damping: 20 }}
        >
          <LinkIcon size={24} weight="bold" />
        </motion.div>
      )
    }

    if (isHovered) {
      if (isRunning) {
        return (
          <motion.div
            key="stop"
            initial={{ scale: 0, rotate: -180, filter: "blur(10px)" }}
            animate={{ scale: 1, rotate: 0, filter: "blur(0px)" }}
            exit={{ scale: 0, rotate: 180, filter: "blur(10px)" }}
            transition={{ type: "spring", stiffness: 300, damping: 20 }}
          >
            <StopIcon size={24} weight="fill" />
          </motion.div>
        )
      } else if (canReset) {
        return (
          <motion.div
            key="reset"
            initial={{ scale: 0, rotate: -180, filter: "blur(10px)" }}
            animate={{ scale: 1, rotate: 0, filter: "blur(0px)" }}
            exit={{ scale: 0, rotate: 180, filter: "blur(10px)" }}
            transition={{ type: "spring", stiffness: 300, damping: 20 }}
          >
            <ArrowCounterClockwiseIcon size={24} weight="bold" />
          </motion.div>
        )
      } else {
        return (
          <motion.div
            key="play"
            initial={{ scale: 0, rotate: -180, filter: "blur(10px)" }}
            animate={{ scale: 1, rotate: 0, filter: "blur(0px)" }}
            exit={{ scale: 0, rotate: 180, filter: "blur(10px)" }}
            transition={{ type: "spring", stiffness: 300, damping: 20 }}
          >
            <PlayIcon size={24} weight="fill" />
          </motion.div>
        )
      }
    }

    return (
      <motion.div
        key="star"
        initial={{ scale: 0, filter: "blur(10px)" }}
        animate={
          isRunning
            ? { rotate: 360, scale: 1, filter: "blur(0px)" }
            : { rotate: 0, scale: 1, filter: "blur(0px)" }
        }
        exit={{ scale: 0, filter: "blur(10px)" }}
        transition={
          isRunning
            ? {
                rotate: {
                  duration: 1,
                  repeat: Infinity,
                  ease: "circInOut",
                },
                scale: { type: "spring", stiffness: 300, damping: 20 },
                filter: { type: "spring", stiffness: 300, damping: 20 },
              }
            : {
                type: "spring",
                stiffness: 300,
                damping: 20,
              }
        }
      >
        <StarFourIcon size={24} weight="fill" />
      </motion.div>
    )
  }

  // Play hover sound effect when Option is pressed and hovering
  useEffect(() => {
    if (isOptionPressed && isHovered && exampleId && !hasPlayedHoverSound) {
      taskSounds.playLinkHover()
      setHasPlayedHoverSound(true)
    } else if (!isOptionPressed || !isHovered) {
      setHasPlayedHoverSound(false)
    }
  }, [isOptionPressed, isHovered, exampleId, hasPlayedHoverSound])

  return (
    <motion.div
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      onMouseDown={() => setIsPressed(true)}
      onMouseUp={() => setIsPressed(false)}
      onClick={handleAction}
      initial={{
        borderColor: "rgba(255, 255, 255, 1)",
      }}
      animate={{
        borderColor: "rgba(255, 255, 255, 0)",
      }}
      className="flex  items-center gap-4 cursor-pointer rounded-lg p-3 -m-3 px-4 -mx-4"
    >
      <motion.div
        animate={{
          scale: isPressed ? 0.95 : isHovered ? 1.05 : 1,
          background: showCheckmark
            ? "#4f46e5" // indigo-600 for success state
            : isOptionPressed && isHovered && exampleId
              ? "#6366f1" // indigo-500 for link copy mode
              : isRunning
                ? TASK_COLORS.running
                : isInterrupted
                  ? TASK_COLORS.interrupted
                  : isCompleted
                    ? TASK_COLORS.success
                    : isFailed
                      ? TASK_COLORS.error
                      : isDeath
                        ? TASK_COLORS.death
                        : TASK_COLORS.idle,
        }}
        transition={{
          scale: { type: "spring", stiffness: 300, damping: 20 },
          background: { duration: 0.2, ease: "easeInOut" },
        }}
        className="w-10 h-10 rounded-md flex items-center justify-center text-white relative overflow-hidden"
      >
        <AnimatePresence mode="popLayout">{getIcon()}</AnimatePresence>

        {isRunning && (
          <motion.div
            className="absolute -inset-0.5 -z-10"
            style={{
              background: `radial-gradient(circle, ${GLOW_COLORS.running} 0%, transparent 70%)`,
            }}
            animate={{
              scale: [1, 1.3, 1],
              opacity: [0.5, 0, 0.5],
            }}
            transition={{
              duration: 2,
              repeat: Infinity,
              ease: "easeInOut",
            }}
          />
        )}

        {/* Glow effect for link copy mode */}
        {isOptionPressed && isHovered && exampleId && !showCheckmark && (
          <motion.div
            className="absolute -inset-0.5 -z-10"
            style={{
              background: `radial-gradient(circle, #6366f1 0%, transparent 70%)`,
            }}
            animate={{
              scale: [1, 1.2, 1],
              opacity: [0.3, 0.1, 0.3],
            }}
            transition={{
              duration: 1.5,
              repeat: Infinity,
              ease: "easeInOut",
            }}
          />
        )}

        {/* Glow effect for success checkmark */}
        {showCheckmark && (
          <motion.div
            className="absolute -inset-0.5 -z-10"
            style={{
              background: `radial-gradient(circle, #4f46e5 0%, transparent 70%)`,
            }}
            animate={{
              scale: [1, 1.4, 1],
              opacity: [0.6, 0, 0.6],
            }}
            transition={{
              duration: 1,
              repeat: Infinity,
              ease: "easeInOut",
            }}
          />
        )}
      </motion.div>

      <div className="flex-1 flex flex-col">
        <h2 className="text-xl font-semibold text-white flex items-baseline gap-2">
          <span>{name}</span>
          {/* <span className="text-neutral-500"> */}
          {/* <CaretDoubleRightIcon size={16} weight="bold" /> */}
          {/* </span> */}
          {variant && <span className="font-medium text-neutral-500">{variant}</span>}
        </h2>
        {description && <p className="text-sm text-neutral-400">{description}</p>}
      </div>
    </motion.div>
  )
}

export const HeaderView = memo(HeaderViewComponent) as typeof HeaderViewComponent


================================================
FILE: src/components/ScheduleTimeline.tsx
================================================
import { motion } from "motion/react"
import React, { useEffect, useRef, useState } from "react"
import type { VisualEffect } from "@/VisualEffect"
import { useVisualEffectState } from "@/VisualEffect"

// Timeline styling configuration
const TIMELINE_CONFIG = {
  // Dimensions
  height: 50,
  lineThickness: 3,
  dotSize: 12,
  cursorWidth: 3,
  cursorHeight: 30,

  // Colors
  colors: {
    running: "bg-blue-500",
    runningActive: "bg-blue-400", // Brighter for active segments
    gap: "bg-neutral-500",
    gapActive: "bg-neutral-500", // Brighter for active segments
    cursor: "bg-white",
    cursorInactive: "bg-neutral-600", // Darker cursor when stopped
    backgroundLine: "bg-neutral-800", // Very dark gray background line
    tickMark: "bg-neutral-800", // Subtle tick marks
  },

  // Raw color values for smooth animations
  rawColors: {
    runningActive: "var(--color-blue-400)",
    runningInactive: "var(--color-blue-500)",
    gapActive: "var(--color-neutral-400)",
    gapInactive: "var(--color-neutral-600)",
    backgroundLine: "var(--color-neutral-800)",
    tickMark: "var(--color-neutral-800)",
    cursorActive: "var(--color-white)",
    cursorInactive: "var(--color-neutral-500)",
  },

  // Positioning
  dotOffset: 6, // Half of dot size for centering
  lineTopOffset: "50%", // Vertical center
  dotTopOffset: "39%", // Slightly above center for visual balance
  leftPadding: 80, // Padding from left edge to avoid fade zone
  startOffset: 50, // Initial offset to make first dot visible

  // Tick marks
  tickMarkWidth: 1, // Width of tick marks
  tickMarkSpacing: 50, // Spacing between tick marks in pixels

  // Animation
  animation: {
    segmentDuration: 0.1,
    cursorDuration: 0.05,
    dotSpring: { type: "spring", visualDuration: 0.5, bounce: 0.4 },
    activeToInactive: {
      type: "spring",
      visualDuration: 0.5,
      bounce: 0.4,
    },
  },
} as const

export interface ScheduleTimelineProps {
  baseEffect: VisualEffect<unknown, unknown>
  repeatEffect: VisualEffect<unknown, unknown>
  className?: string
  pixelsPerSecond?: number
  scrollThreshold?: number
}

interface TrailSegment {
  id: string
  startX: number
  endX: number
  type: "running" | "gap"
  complete: boolean
  startTime?: number // For calculating duration
  endTime?: number
}

export function ScheduleTimeline({
  baseEffect: baseTask,
  className = "",
  pixelsPerSecond = 100,
  repeatEffect: repeatTask,
  scrollThreshold = 0.8,
}: ScheduleTimelineProps) {
  const containerRef = useRef<HTMLDivElement>(null)
  const baseState = useVisualEffectState(baseTask)
  const repeatState = useVisualEffectState(repeatTask)
  const [isActive, setIsActive] = useState(false)
  const [startTime, setStartTime] = useState<number | null>(null)
  const [currentX, setCurrentX] = useState(0)
  const [scrollOffset, setScrollOffset] = useState(0)
  const [trailSegments, setTrailSegments] = useState<Array<TrailSegment>>([])
  const [lastTaskState, setLastTaskState] = useState<string | null>(null)
  const [isClearing, setIsClearing] = useState(false)
  const [, setElapsedTime] = useState(0)
  const [, setFinalElapsedTime] = useState<number | null>(null)

  // Generate unique segment ID
  const generateSegmentId = () => `segment-${Date.now()}-${Math.random()}`

  // Format time function (same as TaskNode)
  const formatTime = (ms: number) => {
    return `${ms}ms`
  }

  // Handle base task state changes and timeline activation
  useEffect(() => {
    // Reset timeline when base task is reset to idle state
    if (baseState.type === "idle" && (isActive || trailSegments.length > 0)) {
      setIsClearing(true)
      setTimeout(() => {
        setIsActive(false)
        setStartTime(null)
        setLastTaskState(null)
        setIsClearing(false)
        setTrailSegments([])
        setCurrentX(TIMELINE_CONFIG.startOffset)
        setScrollOffset(0)
        setElapsedTime(0)
        setFinalElapsedTime(null)
      }, 300)
      return
    }

    // Handle base task state changes for trail coloring
    if (isActive && lastTaskState !== baseState.type && startTime) {
      if (lastTaskState === null && baseState.type === "running") {
        setLastTaskState("running")
        return
      }
      const now = Date.now()
      const elapsed = now - startTime
      const currentPosition = TIMELINE_CONFIG.startOffset + (elapsed / 1000) * pixelsPerSecond

      setTrailSegments(prev => {
        const updated = [...prev]
        if (updated.length > 0) {
          const lastIndex = updated.length - 1
          const last = updated[lastIndex]
          if (last) {
            last.endX = currentPosition
            last.complete = true
            last.endTime = now
          }
        }
        const segmentType = baseState.type === "running" ? "running" : "gap"
        updated.push({
          id: generateSegmentId(),
          startX: currentPosition,
          endX: currentPosition,
          type: segmentType,
          complete: false,
          startTime: now,
        })
        return updated
      })
      setLastTaskState(baseState.type)
    }
  }, [baseState.type, isActive, lastTaskState, startTime, pixelsPerSecond, trailSegments.length])

  // Handle repeat task state changes
  useEffect(() => {
    // Start timeline when repeat task starts running
    if (repeatState.type === "running" && !isActive) {
      setIsActive(true)
      setStartTime(Date.now())
      setCurrentX(TIMELINE_CONFIG.startOffset)
      setScrollOffset(0)
      setTrailSegments([])
      setLastTaskState(null)
      return
    }

    // Stop timeline animation when repeat task completes, fails, or is interrupted
    if (
      (repeatState.type === "completed" ||
        repeatState.type === "failed" ||
        repeatState.type === "interrupted") &&
      isActive
    ) {
      setTrailSegments(prev => {
        const updated = [...prev]
        if (updated.length > 0) {
          const lastIndex = updated.length - 1
          const last = updated[lastIndex]
          if (last) {
            last.complete = true
            last.endTime = Date.now()
          }
        }
        return updated
      })
      if (startTime) {
        setFinalElapsedTime(Date.now() - startTime)
      }
      setIsActive(false)
      return
    }

    // Reset timeline only when tasks are reset to idle state
    if (repeatState.type === "idle" && (isActive || trailSegments.length > 0)) {
      setIsClearing(true)
      setTimeout(() => {
        setIsActive(false)
        setStartTime(null)
        setLastTaskState(null)
        setIsClearing(false)
        setTrailSegments([])
        setCurrentX(TIMELINE_CONFIG.startOffset)
        setScrollOffset(0)
        setElapsedTime(0)
        setFinalElapsedTime(null)
      }, 300)
      return
    }
  }, [repeatState.type, isActive, startTime, trailSegments.length])

  // Animation loop - just moves cursor and extends current segment
  useEffect(() => {
    if (!isActive || !startTime) return

    let animationFrame: number

    const animate = () => {
      const now = Date.now()
      const elapsed = now - startTime

      // Calculate new cursor position
      const newX = TIMELINE_CONFIG.startOffset + (elapsed / 1000) * pixelsPerSecond

      // Update elapsed time
      setElapsedTime(elapsed)

      // Extend the current (last) segment to cursor position
      setTrailSegments(prev => {
        const updated = [...prev]
        if (updated.length > 0) {
          const lastIndex = updated.length - 1
          const last = updated[lastIndex]
          if (last) {
            last.endX = newX
          }
        } else {
          // First segment if none exist - only start when base task is running
          if (baseState.type === "running") {
            updated.push({
              id: generateSegmentId(),
              startX: TIMELINE_CONFIG.startOffset,
              endX: newX,
              type: "running",
              complete: false,
              startTime,
            })
          }
        }
        return updated
      })

      // Handle scrolling when cursor gets near the edge (simplified for now)
      const timelineWidth = containerRef.current?.offsetWidth || 500
      const scrollThresholdX = timelineWidth * scrollThreshold
      if (newX > scrollThresholdX) {
        setScrollOffset(newX - scrollThresholdX)
      }

      setCurrentX(newX)

      animationFrame = requestAnimationFrame(animate)
    }

    animationFrame = requestAnimationFrame(animate)

    return () => {
      cancelAnimationFrame(animationFrame)
    }
  }, [isActive, startTime, baseState.type, pixelsPerSecond, scrollThreshold])

  return (
    <div className={`w-full ${className} relative`} ref={containerRef}>
      {/* Timeline container */}
      {/* Black gradient mask from left to right */}
      {/* <div
        className="absolute pointer-events-none z-20"
        style={{
          left: "-16px",
          right: "0",
          top: "0",
          bottom: "0",
          background:
            "linear-gradient(to right, rgba(0,0,0,1) 0%, transparent 20px)",
        }}
      /> */}

      <div
        className="relative w-full overflow-hidden"
        style={{
          height: `${TIMELINE_CONFIG.height}px`,
        }}
      >
        {/* Content with fade mask */}
        <div className="relative w-full h-full">
          {/* Background line */}
          <div
            className="absolute w-full"
            style={{
              height: `${TIMELINE_CONFIG.lineThickness}px`,
              top: TIMELINE_CONFIG.lineTopOffset,
              transform: "translateY(-50%)",
              backgroundColor: TIMELINE_CONFIG.rawColors.backgroundLine,
            }}
          />

          {/* Tick marks */}
          {(() => {
            const containerWidth = containerRef.current?.offsetWidth || 1000
            const totalWidth = containerWidth + scrollOffset + 500 // Add extra width for scrolling
            const tickCount = Math.ceil(totalWidth / TIMELINE_CONFIG.tickMarkSpacing)
            const ticks = []

            for (let i = 1; i <= tickCount; i++) {
              const x = i * TIMELINE_CONFIG.tickMarkSpacing - scrollOffset
              // Only render ticks that are visible
              if (
                x >= -TIMELINE_CONFIG.tickMarkSpacing &&
                x <= containerWidth + TIMELINE_CONFIG.tickMarkSpacing
              ) {
                ticks.push(
                  <div
                    key={i}
                    className="absolute"
                    style={{
                      left: `${x}px`,
                      width: `${TIMELINE_CONFIG.tickMarkWidth}px`,
                      height: `${TIMELINE_CONFIG.height}px`,
                      top: "0",
                      backgroundColor: TIMELINE_CONFIG.rawColors.tickMark,
                    }}
                  />,
                )
              }
            }

            return ticks
          })()}

          {/* Trail segments */}
          {trailSegments.map(segment => {
            const width = segment.endX - segment.startX
            const left = segment.startX - scrollOffset - 16

            if (segment.type === "running") {
              const isActive = !segment.complete

              return (
                <div key={segment.id}>
                  {/* Blue line */}
                  <motion.div
                    className="absolute"
                    style={{
                      left: `${left}px`,
                      width: `${width}px`,
                      height: `${TIMELINE_CONFIG.lineThickness}px`,
                      top: TIMELINE_CONFIG.lineTopOffset,
                      transform: "translateY(-50%)",
                    }}
                    initial={{ width: 0 }}
                    animate={{
                      width: `${width}px`,
                      backgroundColor: isActive
                        ? TIMELINE_CONFIG.rawColors.runningActive
                        : TIMELINE_CONFIG.rawColors.runningInactive,
                      opacity: isClearing ? 0 : 1,
                    }}
                    transition={{
                      width: {
                        duration: 0, // No delay for width animation
                      },
                      backgroundColor: TIMELINE_CONFIG.animation.activeToInactive,
                      opacity: {
                        duration: 0.3,
                        ease: "easeInOut",
                      },
                    }}
                  />
                  {/* Start dot */}
                  <motion.div
                    className="absolute rounded-full z-12"
                    style={{
                      left: `${left - TIMELINE_CONFIG.dotOffset}px`,
                      width: `${TIMELINE_CONFIG.dotSize}px`,
                      height: `${TIMELINE_CONFIG.dotSize}px`,
                      top: TIMELINE_CONFIG.dotTopOffset,
                      transform: "translateY(-50%)",
                    }}
                    initial={{ scale: 0, opacity: 0 }}
                    animate={{
                      scale: 1,
                      opacity: isClearing ? 0 : 1,
                      backgroundColor: isActive
                        ? TIMELINE_CONFIG.rawColors.runningActive
                        : TIMELINE_CONFIG.rawColors.runningInactive,
                    }}
                    transition={{
                      scale: {
                        ...TIMELINE_CONFIG.animation.dotSpring,
                      },
                      opacity: isClearing
                        ? {
                            duration: 0.3,
                            ease: "easeInOut",
                          }
                        : {
                            ...TIMELINE_CONFIG.animation.dotSpring,
                          },
                      backgroundColor: TIMELINE_CONFIG.animation.activeToInactive,
                    }}
                  />
                  {/* End dot (only if segment is complete) */}
                  {segment.complete && (
                    <motion.div
                      className="absolute rounded-full z-12"
                      style={{
                        left: `${left + width - TIMELINE_CONFIG.dotOffset}px`,
                        width: `${TIMELINE_CONFIG.dotSize}px`,
                        height: `${TIMELINE_CONFIG.dotSize}px`,
                        top: TIMELINE_CONFIG.dotTopOffset,
                        transform: "translateY(-50%)",
                      }}
                      initial={{
                        scale: 0,
                        opacity: 0,
                        backgroundColor: TIMELINE_CONFIG.rawColors.runningActive, // Start with active color
                      }}
                      animate={{
                        scale: 1,
                        opacity: isClearing ? 0 : 1,
                        backgroundColor: TIMELINE_CONFIG.rawColors.runningInactive, // Animate to inactive color
                      }}
                      transition={{
                        scale: {
                          ...TIMELINE_CONFIG.animation.dotSpring,
                        },
                        opacity: isClearing
                          ? {
                              duration: 0.3,
                              ease: "easeInOut",
                            }
                          : {
                              ...TIMELINE_CONFIG.animation.dotSpring,
                            },
                        backgroundColor: TIMELINE_CONFIG.animation.activeToInactive,
                        borderColor: TIMELINE_CONFIG.animation.activeToInactive,
                      }}
                    />
                  )}
                </div>
              )
            } else {
              const isActive = !segment.complete
              // Calculate current duration - either completed or elapsed so far
              let segmentDuration = null
              if (segment.startTime) {
                if (segment.endTime) {
                  // Completed segment
                  segmentDuration = segment.endTime - segment.startTime
                } else if (isActive && startTime) {
                  // Active segment - show elapsed time
                  segmentDuration = Date.now() - segment.startTime
                }
              }

              // Calculate clean positions without all the offset confusion
              const segmentStartX = segment.startX - scrollOffset
              const segmentEndX = segment.endX - scrollOffset
              const segmentWidth = segmentEndX - segmentStartX

              return (
                <React.Fragment key={segment.id}>
                  {/* Gap line */}
                  <motion.div
                    className="absolute rounded-full"
                    style={{
                      left: `${segmentStartX - 16}px`, // Apply -16 offset only here
                      width: `${segmentWidth}px`,
                      height: `${TIMELINE_CONFIG.lineThickness}px`,
                      top: TIMELINE_CONFIG.lineTopOffset,
                      transform: "translateY(-50%)",
                    }}
                    initial={{ width: 0 }}
                    animate={{
                      width: `${segmentWidth}px`,
                      backgroundColor: isActive
                        ? TIMELINE_CONFIG.rawColors.gapActive
                        : TIMELINE_CONFIG.rawColors.gapInactive,
                      opacity: isClearing ? 0 : 1,
                    }}
                    transition={{
                      width: {
                        duration: 0, // No delay for width animation
                      },
                      backgroundColor: TIMELINE_CONFIG.animation.activeToInactive,
                      opacity: {
                        duration: 0.3,
                        ease: "easeInOut",
                      },
                    }}
                  />

                  {/* Duration label for gap segments */}
                  {segmentDuration !== null && segmentWidth > 50 && (
                    <div
                      className="absolute pointer-events-none"
                      style={{
                        left: `${segmentStartX - 16 + segmentWidth / 2}px`,
                        top: `${TIMELINE_CONFIG.height / 2}px`,
                        transform: "translate(-50%, -50%)",
                        display: "flex",
                        alignItems: "center",
                        justifyContent: "center",
                      }}
                    >
                      <motion.div
                        className="bg-neutral-900/90 px-2 py-0.5 rounded text-xs font-mono text-neutral-300 border border-neutral-700 whitespace-nowrap"
                        initial={{ opacity: 0, scale: 0.8 }}
                        animate={{
                          opacity: isClearing ? 0 : 1,
                          scale: 1,
                        }}
                        transition={{
                          duration: 0.3,
                          ease: "easeOut",
                        }}
                      >
                        {formatTime(segmentDuration)}
                      </motion.div>
                    </div>
                  )}
                </React.Fragment>
              )
            }
          })}

          {/* Cursor - vertical line that changes color when stopped */}
          <motion.div
            className="absolute"
            style={{
              left: `${currentX - scrollOffset - 16}px`,
              width: `${TIMELINE_CONFIG.cursorWidth}px`,
              height: `${TIMELINE_CONFIG.height}px`,
              top: "0",
            }}
            animate={{
              left: `${currentX - scrollOffset - 16}px`,
              backgroundColor: isActive
                ? TIMELINE_CONFIG.rawColors.cursorActive
                : TIMELINE_CONFIG.rawColors.cursorInactive,
              opacity: isClearing ? 0 : 1,
            }}
            transition={{
              left: {
                duration: TIMELINE_CONFIG.animation.cursorDuration,
                ease: "linear",
              },
              backgroundColor: {
                duration: 0.3,
                ease: "easeInOut",
              },
              opacity: {
                duration: 0.8,
                ease: "easeInOut",
              },
            }}
          />
        </div>
      </div>
    </div>
  )
}


================================================
FILE: src/components/Timer.tsx
================================================
import { useEffect, useRef, useState } from "react"
import type { VisualEffect } from "@/VisualEffect"

function useTimer(task: VisualEffect<unknown, unknown>) {
  const [elapsedTime, setElapsedTime] = useState(0)
  const intervalRef = useRef<number | null>(null)

  useEffect(() => {
    if (task.showTimer && task.state.type === "running" && task.startTime) {
      const start = task.startTime
      const updateTimer = () => {
        const now = Date.now()
        setElapsedTime(now - (start ?? now))
      }

      updateTimer()

      intervalRef.current = window.setInterval(updateTimer, 10)
    } else if (task.showTimer && task.endTime && task.startTime) {
      setElapsedTime(task.endTime - task.startTime)
    } else if (task.state.type === "idle") {
      setElapsedTime(0)
    }

    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current)
        intervalRef.current = null
      }
    }
  }, [task.showTimer, task.state.type, task.startTime, task.endTime])

  return elapsedTime
}

export function Timer({ effect: task }: { effect: VisualEffect<unknown, unknown> }) {
  const elapsedTime = useTimer(task)

  const shouldShowTimer =
    task.showTimer &&
    (task.state.type === "running" ||
      task.state.type === "completed" ||
      task.state.type === "failed" ||
      task.state.type === "interrupted" ||
      task.state.type === "death")

  const formatTime = (ms: number) => {
    if (ms < 1000) {
      return `${ms}ms`
    } else {
      return `${(ms / 1000).toFixed(1)}s`
    }
  }

  if (shouldShowTimer) {
    return <span className="font-mono">{formatTime(elapsedTime)}</span>
  }

  return <span>{task.name}</span>
}


================================================
FILE: src/components/display/EffectExample.tsx
================================================
"use client"

import { ArrowRightIcon } from "@phosphor-icons/react"
import { motion } from "motion/react"
import { memo, useCallback, useEffect, useRef, useState } from "react"
import type { VisualEffect } from "../../VisualEffect"
import type { VisualRef } from "../../VisualRef"
import type { VisualScope } from "../../VisualScope"
import { CodeBlock } from "../CodeBlock"
import { EffectNode } from "../effect"
import { FloatingHighlight } from "../feedback"
import { HeaderView } from "../HeaderView"
import { ScopeStack } from "../scope/ScopeStack"
import { RefDisplay, ScheduleTimeline } from "./"

export interface EffectHighlight {
  text: string
}

export interface EffectExampleProps<A, E> {
  name: string
  variant?: string
  description: React.ReactNode
  code: string
  effects: Array<VisualEffect<unknown, unknown>>
  resultEffect?: VisualEffect<A, E>
  effectHighlightMap: Record<string, EffectHighlight | undefined>
  index?: number
  showScheduleTimeline?: boolean
  isDarkMode?: boolean
  configurationPanel?: React.ReactNode
  refs?: Array<VisualRef<unknown>>
  scope?: VisualScope
  exampleId: string
}

// Default empty array to avoid recreating on every render
const EMPTY_REFS_ARRAY: Array<VisualRef<unknown>> = []

function EffectExampleComponent<A, E>({
  code,
  configurationPanel,
  description,
  exampleId,
  isDarkMode = false,
  name,
  refs = EMPTY_REFS_ARRAY,
  resultEffect,
  scope,
  showScheduleTimeline,
  effectHighlightMap,
  effects,
  variant,
}: EffectExampleProps<A, E>) {
  const [hoveredEffect, setHoveredEffect] = useState<string | null>(null)
  const [delayedHoveredEffect, setDelayedHoveredEffect] = useState<string | null>(null)
  const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const codeContainerRef = useRef<HTMLDivElement>(null)

  // Memoize hover handlers to prevent re-creation on every render
  const handleMouseEnter = useCallback((effectName: string) => {
    setHoveredEffect(effectName)
  }, [])

  const handleMouseLeave = useCallback(() => {
    setHoveredEffect(null)
  }, [])

  // Handle hover with delay
  useEffect(() => {
    // Clear any existing timeout
    if (hoverTimeoutRef.current) {
      clearTimeout(hoverTimeoutRef.current)
      hoverTimeoutRef.current = null
    }

    if (hoveredEffect) {
      // Immediately show highlight when hovering
      setDelayedHoveredEffect(hoveredEffect)
    } else {
      // Delay hiding the highlight
      hoverTimeoutRef.current = setTimeout(() => {
        setDelayedHoveredEffect(null)
      }, 500) // 500ms delay before hiding
    }

    return () => {
      if (hoverTimeoutRef.current) {
        clearTimeout(hoverTimeoutRef.current)
      }
    }
  }, [hoveredEffect])

  // Determine if this is a single effect example
  const isSingleEffect = !resultEffect || (effects.length === 1 && effects[0] === resultEffect)
  const headerEffect = resultEffect || effects[0]

  if (!headerEffect) {
    throw new Error("EffectExample requires at least one effect")
  }

  // Shared UI values
  const borderColorValue = isDarkMode ? "rgba(127, 29, 29, 0.5)" : "rgba(64, 64, 64, 0.5)"
  const backgroundGradient = isDarkMode
    ? "linear-gradient(to bottom right, black, rgba(127, 29, 29, 0.2))"
    : "linear-gradient(to bottom right, rgba(23, 23, 23, 0.8), rgba(23, 23, 23, 0.4))"
  const headerBackground = isDarkMode ? "rgba(0, 0, 0, 0.5)" : "rgba(38, 38, 38, 0.5)"
  const standardTransition = { duration: 0.2, ease: "easeInOut" as const }

  const highlightTarget = delayedHoveredEffect
    ? effectHighlightMap[delayedHoveredEffect] || null
    : null

  // If the result node should display elapsed ms from a timer-enabled effect, prefer its own timer,
  // otherwise fall back to the first input effect that has a timer.
  const labelEffectForResult: VisualEffect<unknown, unknown> | undefined = resultEffect?.showTimer
    ? (resultEffect as unknown as VisualEffect<unknown, unknown>)
    : effects.find(e => e.showTimer)

  return (
    <motion.div
      className={`w-full flex flex-col border rounded-2xl shadow-2xl relative `}
      initial={{
        boxShadow: isDarkMode ? `0 0 40px rgba(220, 38, 38, 0.3)` : `0 0 0 0 rgba(59, 130, 250, 0)`,
        borderColor: borderColorValue,
        background: backgroundGradient,
      }}
      animate={{
        boxShadow: isDarkMode ? `0 0 40px rgba(220, 38, 38, 0.3)` : `0 0 0 0 rgba(59, 130, 250, 0)`,
        borderColor: borderColorValue,
        background: backgroundGradient,
      }}
      transition={{
        borderColor: standardTransition,
        background: standardTransition,
      }}
    >
      {/* Header with interactive controls */}
      <motion.div
        className={`p-4 border-b rounded-t-2xl`}
        initial={{
          borderColor: borderColorValue,
          backgroundColor: headerBackground,
        }}
        animate={{
          borderColor: borderColorValue,
          backgroundColor: headerBackground,
        }}
        transition={standardTransition}
      >
        <HeaderView
          effect={headerEffect}
          name={name}
          {...(variant && { variant })}
          description={description}
          refs={refs}
          exampleId={exampleId}
        />
      </motion.div>

      {/* Configuration Panel */}
      {configurationPanel && (
        <motion.div
          initial={{
            borderColor: borderColorValue,
          }}
          animate={{
            borderColor: borderColorValue,
          }}
          transition={standardTransition}
          className="border-b"
        >
          {configurationPanel}
        </motion.div>
      )}

      {/* Refs display */}
      {refs.length > 0 && (
        <motion.div
          className="p-4 border-b"
          initial={{
            borderColor: borderColorValue,
          }}
          animate={{
            borderColor: borderColorValue,
          }}
          transition={standardTransition}
        >
          <div className="flex flex-wrap gap-3">
            {refs.map(ref => (
              <RefDisplay key={ref.name} visualRef={ref} />
            ))}
          </div>
        </motion.div>
      )}

      {/* Main visualization */}
      <motion.div
        className={`px-4 py-5 border-b`}
        initial={{
          borderColor: borderColorValue,
        }}
        animate={{
          borderColor: borderColorValue,
        }}
        transition={standardTransition}
      >
        {isSingleEffect ? (
          // Single effect - just show the effect
          <div className="flex justify-start">
            <div
              onMouseEnter={() => handleMouseEnter(headerEffect.name)}
              onMouseLeave={handleMouseLeave}
            >
              <EffectNode effect={headerEffect} />
            </div>
          </div>
        ) : (
          // Multiple effects with arrow and result
          <div className="flex flex-row items-center justify-start gap-6">
            {/* Input effects - wrap on mobile */}
            <div className="flex flex-wrap justify-center gap-6">
              {effects.map(effect => (
                <div
                  key={effect.name}
                  onMouseEnter={() => handleMouseEnter(effect.name)}
                  onMouseLeave={handleMouseLeave}
                >
                  <EffectNode effect={effect} />
                </div>
              ))}
            </div>

            {/* Arrow - rotate on mobile */}
            <div className="text-neutral-500 rotate-0 flex items-center relative top-[-13px]">
              <ArrowRightIcon size={24} weight="fill" />
            </div>

            {/* Result */}
            {resultEffect && (
              <div
                onMouseEnter={() => handleMouseEnter(resultEffect.name)}
                onMouseLeave={handleMouseLeave}
              >
                <EffectNode
                  effect={resultEffect}
                  {...(labelEffectForResult && { labelEffect: labelEffectForResult })}
                />
              </div>
            )}
          </div>
        )}
      </motion.div>

      {/* Schedule timeline (if provided) */}
      {showScheduleTimeline && effects[0] && resultEffect && (
        <motion.div
          initial={{
            borderColor: borderColorValue,
          }}
          animate={{
            borderColor: borderColorValue,
          }}
          transition={standardTransition}
          className="border-b"
        >
          <ScheduleTimeline baseEffect={effects[0]} repeatEffect={resultEffect} />
        </motion.div>
      )}

      {/* Scope visualization (if provided) */}
      {scope && (
        <motion.div
          initial={{
            borderColor: borderColorValue,
          }}
          animate={{
            borderColor: borderColorValue,
          }}
          transition={standardTransition}
          className="border-b"
        >
          <ScopeStack scope={scope} />
        </motion.div>
      )}

      {/* Code block */}
      <div
        className="relative p-4 text-base"
        ref={codeContainerRef}
        style={{ position: "relative" }}
      >
        <CodeBlock code={code} activeLines={[]} />
        <FloatingHighlight
          containerRef={codeContainerRef as React.RefObject<HTMLDivElement>}
          target={highlightTarget}
        />
      </div>
    </motion.div>
  )
}

// Helper function to compare arrays by reference and length
function areArraysEqual<T>(a: T[] | undefined, b: T[] | undefined): boolean {
  if (a === b) return true
  if (!a || !b) return a === b
  if (a.length !== b.length) return false
  return a.every((item, index) => item === b[index])
}

// Memoized component with custom comparison function
export const EffectExample = memo(EffectExampleComponent, (prevProps, nextProps) => {
  // Compare all props except functions and objects that might have new references
  return (
    prevProps.name === nextProps.name &&
    prevProps.variant === nextProps.variant &&
    prevProps.code === nextProps.code &&
    prevProps.index === nextProps.index &&
    prevProps.showScheduleTimeline === nextProps.showScheduleTimeline &&
    prevProps.isDarkMode === nextProps.isDarkMode &&
    areArraysEqual(prevProps.effects, nextProps.effects) &&
    prevProps.resultEffect === nextProps.resultEffect &&
    areArraysEqual(prevProps.refs, nextProps.refs) &&
    prevProps.scope === nextProps.scope &&
    prevProps.exampleId === nextProps.exampleId &&
    prevProps.effectHighlightMap === nextProps.effectHighlightMap
  )
}) as typeof EffectExampleComponent


================================================
FILE: src/components/display/RefDisplay.tsx
================================================
import { AnimatePresence, motion, type Transition } from "motion/react"
import { useVisualRef, type VisualRef } from "@/VisualRef"

interface RefDisplayProps<A> {
  visualRef: VisualRef<A>
  style?: React.CSSProperties
}

export function RefDisplay<A>({ style = {}, visualRef }: RefDisplayProps<A>) {
  const { justChanged, value } = useVisualRef(visualRef)

  // Dynamic transition: quick flash in (50ms) then slow fade out (600ms)
  const transition: Transition = {
    visualDuration: justChanged ? 0.1 : 0.3,
    bounce: 0,
    type: "spring",
  }

  return (
    <div className="flex" style={{ ...style, position: "relative", flex: "1 1 auto" }}>
      <motion.div
        className="flex items-center  rounded-lg border"
        initial={{
          backgroundColor: "rgba(38, 38, 38, 0.8)",
          borderColor: "rgba(64, 64, 64, 0.5)",
        }}
        animate={{
          backgroundColor: justChanged
            ? "rgba(59, 130, 246, 0.3)" // brighter on flash
            : "rgba(38, 38, 38, 0.8)",
          borderColor: justChanged ? "rgba(59, 130, 246, 1)" : "rgba(64, 64, 64, 0.5)",
        }}
        transition={transition}
      >
        {/* Ref name */}
        <span className="text-md font-medium whitespace-nowrap text-neutral-400 p-2 px-4">
          {visualRef.name}
        </span>

        {/* Vertical separator */}
        <motion.span
          className="w-px h-full "
          initial={{ backgroundColor: "rgba(64, 64, 64, 0.5)" }}
          animate={{
            backgroundColor: justChanged ? "rgba(59, 130, 246, 0.3)" : "rgba(64, 64, 64, 0.5)",
          }}
          exit={{
            backgroundColor: "rgba(64, 64, 64, 0.5)",
            transition: { duration: 0.6, ease: "easeInOut" },
          }}
          transition={transition}
        />

        {/* Ref value with odometer-style transition */}
        <div className="text-md font-mono font-semibold text-neutral-100 min-w-0 overflow-hidden p-2 px-4">
          <AnimatePresence mode="popLayout" initial={false}>
            <motion.span
              key={String(value)}
              initial={{ y: -8, opacity: 0, filter: "blur(4px)" }}
              animate={{ y: 0, opacity: 1, filter: "blur(0px)" }}
              exit={{ y: 8, opacity: 0, filter: "blur(4px)" }}
              transition={{ duration: 0.35, ease: "easeOut" }}
              className="inline-block"
            >
              {String(value)}
            </motion.span>
          </AnimatePresence>
        </div>
      </motion.div>
    </div>
  )
}


================================================
FILE: src/components/display/index.ts
================================================
export { ScheduleTimeline } from "../ScheduleTimeline"
export { EffectExample } from "./EffectExample"
export { RefDisplay } from "./RefDisplay"


================================================
FILE: src/components/effect/EffectContainer.tsx
================================================
import { motion, useTransform } from "motion/react"
import { colors, effects } from "@/animations"
import type { VisualEffect } from "../../VisualEffect"
import { nodeVariants } from "./nodeVariants"
import { getTaskShadow } from "./taskUtils"
import type { EffectMotionValues } from "./useEffectMotion"

interface EffectContainerProps {
  state: VisualEffect<unknown, unknown>["state"]
  motionValues: Pick<
    EffectMotionValues,
    | "nodeWidth"
    | "nodeHeight"
    | "borderRadius"
    | "rotation"
    | "shakeX"
    | "shakeY"
    | "blurAmount"
    | "glowIntensity"
  >
  onMouseEnter?: () => void
  onMouseLeave?: () => void
  children: React.ReactNode
}

export function EffectContainer({
  motionValues,
  children,
  onMouseEnter,
  onMouseLeave,
  state,
}: EffectContainerProps) {
  const isDeath = state.type === "death"
  // Use variants for static state-based properties
  const current = state.type as keyof typeof nodeVariants

  return (
    <motion.div
      // Hybrid approach: variants handle static properties
      variants={nodeVariants}
      animate={current}
      initial={false}
      style={{
        // Imperative motion values drive dynamic sizing
        width: motionValues.nodeWidth,
        height: motionValues.nodeHeight,
        borderRadius: motionValues.borderRadius,
        position: "absolute",
        overflow: "hidden",
        // Variants still own scale, opacity, background color
        rotate: motionValues.rotation,
        x: motionValues.shakeX,
        y: motionValues.shakeY,
        cursor: "auto",
        border: isDeath ? `2px solid ${colors.border.death}` : `1px solid ${colors.border.default}`,
        // Promote to its own GPU layer and limit reflows/paints
        contain: "layout style paint", // restrict the scope of layout and paint work
        willChange: "transform, filter",
        transform: "translateZ(0)", // ensure GPU compositing

        filter: useTransform([motionValues.blurAmount], ([blur = 0]: Array<number>) => {
          // Cap blur radius to 2px max for better performance
          const cappedBlur = Math.min(blur, 2)

          return isDeath
            ? `blur(${cappedBlur}px) contrast(${effects.death.contrast}) brightness(${effects.death.brightness})`
            : `blur(${cappedBlur}px)`
        }),
        // Use box-shadow for glow instead of expensive drop-shadow
        boxShadow: useTransform([motionValues.glowIntensity], ([glow = 0]: Array<number>) => {
          const cappedGlow = Math.min(glow, 8)
          const baseGlow = getTaskShadow(state)

          if (isDeath) {
            return cappedGlow > 0
              ? `${baseGlow}, 0 0 ${cappedGlow * 2}px ${colors.glow.death}`
              : baseGlow
          }

          return cappedGlow > 0
            ? `${baseGlow}, 0 0 ${cappedGlow}px ${colors.glow.running}`
            : baseGlow
        }),
      }}
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
    >
      {children}
    </motion.div>
  )
}


================================================
FILE: src/components/effect/EffectContent.tsx
================================================
import { SkullIcon, StarFourIcon, WarningOctagonIcon } from "@phosphor-icons/react"
import { AnimatePresence, motion } from "motion/react"
import { useLayoutEffect, useRef } from "react"
import { springs } from "@/animations"
import { theme } from "../../theme"
import type { VisualEffect } from "../../VisualEffect"
import { isRenderableResult, renderResult } from "../renderers"
import type { EffectMotionValues } from "./useEffectMotion"

type EffectState = VisualEffect<unknown, unknown>["state"]

interface EffectContentProps {
  state: EffectState
  motionValues: Pick<EffectMotionValues, "contentOpacity" | "contentScale" | "nodeWidth">
}

function TaskIcon({ size, type }: { type: string; size: number }) {
  const iconSize = size * 0.5
  const iconProps = {
    size: iconSize,
    color: "rgba(255, 255, 255, 0.9)",
  }

  switch (type) {
    case "failed":
      return <SkullIcon {...iconProps} weight="fill" />
    case "death":
      return <SkullIcon {...iconProps} weight="fill" color="#dc2626" />
    case "interrupted":
      return <WarningOctagonIcon {...iconProps} weight="fill" />
    default:
      return <StarFourIcon {...iconProps} weight="fill" />
  }
}

function TaskContentInner({ state }: { state: EffectState }) {
  switch (state.type) {
    case "failed":
    case "interrupted":
    case "death":
      return (
        <motion.div
          key={state.type}
          initial={{ scale: 0, filter: "blur(10px)" }}
          animate={{ scale: 1, filter: "blur(0px)" }}
          exit={{ scale: 0, filter: "blur(10px)" }}
          transition={{
            type: "spring",
            bounce: state.type === "interrupted" ? 0.5 : 0.3,
            visualDuration: 0.3,
          }}
        >
          <TaskIcon type={state.type} size={64} />
        </motion.div>
      )

    case "completed": {
      const { result } = state
      const content = isRenderableResult(result) ? renderResult(result) : String(result)

      return (
        <motion.div
          key="result"
          initial={{ opacity: 0, scale: 0.5, filter: "blur(10px)" }}
          animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
          exit={{ opacity: 0, scale: 0.5, filter: "blur(10px)" }}
          transition={{
            ...springs.bouncy,
            stiffness: 260,
            damping: 18,
          }}
        >
          {content}
        </motion.div>
      )
    }

    case "running":
      return null

    default:
      return (
        <motion.div
          key="star"
          initial={{ scale: 0, filter: "blur(10px)" }}
          animate={{ scale: 1, filter: "blur(0px)" }}
          exit={{ scale: 0, filter: "blur(10px)" }}
          transition={{ type: "spring", bounce: 0.3, visualDuration: 0.3 }}
        >
          <TaskIcon type="default" size={64} />
        </motion.div>
      )
  }
}

export function EffectContent({ motionValues, state }: EffectContentProps) {
  const contentRef = useRef<HTMLDivElement>(null)

  // Auto-resize width based on content (synchronous, before paint)
  useLayoutEffect(() => {
    if (state.type === "completed" && contentRef.current) {
      const actualWidth = contentRef.current.scrollWidth

      if (actualWidth > 64 - 16) {
        motionValues.nodeWidth.set(actualWidth + 24)
      }
    }
  }, [state, motionValues.nodeWidth])

  return (
    <motion.div
      style={{
        position: "absolute",
        inset: 0,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        fontWeight: 600,
        color: theme.colors.textPrimary,
        opacity: motionValues.contentOpacity,
        scale: motionValues.contentScale,
        padding: "0 8px",
      }}
    >
      <div ref={contentRef} style={{ whiteSpace: "nowrap" }}>
        <AnimatePresence mode="popLayout">
          <TaskContentInner state={state} />
        </AnimatePresence>
      </div>
    </motion.div>
  )
}


================================================
FILE: src/components/effect/EffectLabel.tsx
================================================
import { motion } from "motion/react"
import { Timer } from "@/components/Timer"
import { theme } from "@/theme"
import type { VisualEffect } from "@/VisualEffect"
import { useVisualEffectState } from "@/VisualEffect"

interface EffectLabelProps {
  effect: VisualEffect<unknown, unknown>
}

export function EffectLabel({ effect }: EffectLabelProps) {
  const state = useVisualEffectState(effect)

  return (
    <motion.div
      style={{
        marginTop: theme.spacing.sm,
        fontSize: "0.75rem",
        textAlign: "center",
        fontWeight: 500,
        color: theme.colors.textMuted,
      }}
      animate={{
        color: state.type === "idle" ? theme.colors.textMuted : theme.colors.textSecondary,
      }}
      transition={{ duration: 0.3 }}
    >
      <Timer effect={effect} />
    </motion.div>
  )
}


================================================
FILE: src/components/effect/EffectNode.tsx
================================================
import { AnimatePresence, motion } from "motion/react"
import { memo, useCallback, useState } from "react"
import {
  useVisualEffectNotification,
  useVisualEffectState,
  type VisualEffect,
} from "@/VisualEffect"
import { DeathBubble } from "../feedback/DeathBubble"
import { FailureBubble } from "../feedback/FailureBubble"
import { NotificationBubble } from "../feedback/NotificationBubble"
import { EffectContainer } from "./EffectContainer"
import { EffectContent } from "./EffectContent"
import { EffectLabel } from "./EffectLabel"
import { EffectOverlay } from "./EffectOverlay"
import {
  useEffectAnimations,
  useEffectMotion,
  useRunningAnimation,
  useStateAnimations,
} from "./useEffectMotion"

function EffectNodeComponent<A, E>({
  style = {},
  effect,
  labelEffect,
}: {
  effect: VisualEffect<A, E>
  style?: React.CSSProperties
  labelEffect?: VisualEffect<unknown, unknown>
}) {
  const notification = useVisualEffectNotification(effect)
  const state = useVisualEffectState(effect)
  const effectMotion = useEffectMotion()
  const isRunning = state.type === "running"
  const isFailedOrDeath = state.type === "failed" || state.type === "death"

  // State for error bubble visibility
  const [showErrorBubble, setShowErrorBubble] = useState(false)
  const [isHovering, setIsHovering] = useState(false)

  // Apply all animations
  useRunningAnimation(isRunning, effectMotion)
  useStateAnimations(state, effectMotion)
  useEffectAnimations(state, effectMotion, isHovering, setShowErrorBubble)

  // Stable mouse handlers to avoid creating new functions on every render
  const handleMouseEnter = useCallback(() => {
    setIsHovering(true)
    if (isFailedOrDeath) setShowErrorBubble(true)
  }, [isFailedOrDeath])

  const handleMouseLeave = useCallback(() => {
    setIsHovering(false)
  }, [])

  return (
    <div style={{ ...style, position: "relative" }}>
      {/* Error bubble positioned outside container */}
      <AnimatePresence>
        {isFailedOrDeath &&
          showErrorBubble &&
          (state.type === "failed" ? (
            <FailureBubble error={state.error} />
          ) : (
            <DeathBubble error={state.error} />
          ))}
      </AnimatePresence>

      {/* Notification bubbles - hidden when error bubbles are shown */}
      <AnimatePresence>
        {!isFailedOrDeath && notification && (
          <NotificationBubble key={notification.id} notification={notification} />
        )}
      </AnimatePresence>

      <motion.div
        style={{
          width: effectMotion.nodeWidth,
          height: 64,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          position: "relative",
        }}
      >
        <EffectContainer
          state={state}
          motionValues={effectMotion}
          onMouseEnter={handleMouseEnter}
          onMouseLeave={handleMouseLeave}
        >
          <EffectOverlay isRunning={isRunning} motionValues={effectMotion} />
          <EffectContent state={state} motionValues={effectMotion} />
        </EffectContainer>
      </motion.div>
      <EffectLabel effect={labelEffect ?? effect} />
    </div>
  )
}

export const EffectNode = memo(EffectNodeComponent) as typeof EffectNodeComponent


================================================
FILE: src/components/effect/EffectOverlay.tsx
================================================
import { motion } from "motion/react"
import { theme } from "../../theme"
import type { EffectMotionValues } from "./useEffectMotion"

interface EffectOverlayProps {
  isRunning: boolean
  motionValues: Pick<
    EffectMotionValues,
    "borderRadius" | "borderOpacity" | "flashOpacity" | "flashColor"
  >
}

function RunningOverlay() {
  return (
    <>
      {[0, 0.2, 0.4, 0.6, 0.8, 1].map((delay, i) => (
        <motion.div
          key={i}
          style={{
            position: "absolute",
            top: 0,
            left: 0,
            bottom: 0,
            width: "200%",
            background:
              "linear-gradient(90deg, transparent 0%, transparent 40%, rgba(255,255,255,0.1) 45%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0.1) 55%, transparent 60%, transparent 100%)",
            filter: "blur(4px)",
            mixBlendMode: "lighten",
          }}
          animate={{
            x: ["-66.0%", "50%"],
          }}
          transition={{
            duration: 0.8,
            delay,
            repeat: Infinity,
            ease: [0.5, 0, 0.1, 1],
          }}
        />
      ))}
    </>
  )
}

export function EffectOverlay({ motionValues, isRunning }: EffectOverlayProps) {
  return (
    <>
      {/* Animated border overlay for running state */}
      {isRunning && (
        <motion.div
          style={{
            position: "absolute",
            inset: 0,
            borderRadius: motionValues.borderRadius,
            boxShadow: "inset 0 0 0 1px rgba(100, 200, 255, 0.8)",
            opacity: motionValues.borderOpacity,
            pointerEvents: "none",
          }}
        />
      )}

      {/* Running animation overlay */}
      {isRunning && <RunningOverlay />}

      {/* Flash effect overlay */}
      <motion.div
        style={{
          position: "absolute",
          inset: 0,
          borderRadius: theme.radius.md,
          background: motionValues.flashColor,
          mixBlendMode: "overlay",
          opacity: motionValues.flashOpacity,
          pointerEvents: "none",
        }}
      />
    </>
  )
}


================================================
FILE: src/components/effect/index.ts
================================================
export { EffectContainer } from "./EffectContainer"
export { EffectContent } from "./EffectContent"
export { EffectLabel } from "./EffectLabel"
export { EffectNode } from "./EffectNode"
export { EffectOverlay } from "./EffectOverlay"
export { nodeVariants } from "./nodeVariants"
export * from "./taskUtils"
export type { EffectMotionValues } from "./useEffectMotion"
export {
  useEffectAnimations,
  useEffectMotion,
  useRunningAnimation,
  useStateAnimations,
} from "./useEffectMotion"


================================================
FILE: src/components/effect/nodeVariants.ts
================================================
import { springs } from "@/animations"
import { TASK_COLORS } from "../../constants/colors"

// Hybrid approach: Only handle static state-based properties in variants
// Keep complex animations (jitter, pulses, flashes, dynamic sizing) imperative
export const nodeVariants = {
  idle: {
    scale: 1,
    opacity: 0.6,
    backgroundColor: TASK_COLORS.idle,
    transition: {
      // Fast color change to match original
      backgroundColor: { duration: 0.1, ease: "easeInOut" },
      // Keep spring for scale/opacity
      scale: springs.default,
      opacity: springs.default,
    },
  },

  running: {
    scale: 0.95,
    opacity: 1,
    backgroundColor: TASK_COLORS.running,
    transition: {
      backgroundColor: { duration: 0.1, ease: "easeInOut" },
      scale: springs.default,
      opacity: springs.default,
    },
  },

  completed: {
    scale: 1,
    opacity: 1,
    backgroundColor: TASK_COLORS.success,
    transition: {
      backgroundColor: { duration: 0.1, ease: "easeInOut" },
      scale: springs.contentScale,
      opacity: springs.contentScale,
    },
  },

  failed: {
    backgroundColor: TASK_COLORS.error,
    scale: 1,
    opacity: 1,
    transition: {
      backgroundColor: { duration: 0.1, ease: "easeInOut" },
      scale: springs.contentScale,
      opacity: springs.contentScale,
    },
  },

  death: {
    backgroundColor: TASK_COLORS.death,
    scale: 1,
    opacity: 1,
    transition: {
      backgroundColor: { duration: 0.1, ease: "easeInOut" },
      scale: springs.contentScale,
      opacity: springs.contentScale,
    },
  },

  interrupted: {
    backgroundColor: TASK_COLORS.interrupted,
    opacity: 1,
    scale: 1,
    transition: {
      backgroundColor: { duration: 0.1, ease: "easeInOut" },
      scale: springs.default,
      opacity: springs.default,
    },
  },
} as const

// Note: Width, height, and complex animations remain imperative
// This preserves dynamic width expansion and complex timing sequences


================================================
FILE: src/components/effect/taskUtils.ts
================================================
import { SHADOW_COLORS } from "../../constants/colors"
import { theme } from "../../theme"
import type { VisualEffect } from "../../VisualEffect"

type TaskState = VisualEffect<unknown, unknown>["state"]

export function getTaskShadow(state: TaskState): string {
  switch (state.type) {
    case "running":
      return SHADOW_COLORS.running
    default:
      return theme.shadow.sm
  }
}


================================================
FILE: src/components/effect/useEffectMotion.ts
================================================
import {
  type AnimationPlaybackControls,
  animate,
  type MotionValue,
  useMotionValue,
  useSpring,
  useTransform,
  useVelocity,
} from "motion/react"
import { useEffect, useLayoutEffect, useRef, useState } from "react"
import { colors, effects, shake, springs, timing } from "@/animations"
import { dimensions } from "@/constants/dimensions"
import { useStateTransition } from "../../hooks/useStateTransition"
import { theme } from "../../theme"
import type { VisualEffect } from "../../VisualEffect"

type EffectState = VisualEffect<unknown, unknown>["state"]

export interface EffectMotionValues {
  nodeWidth: MotionValue<number>
  nodeHeight: MotionValue<number>
  contentOpacity: MotionValue<number>
  flashOpacity: MotionValue<number>
  flashColor: MotionValue<string>
  borderRadius: MotionValue<number>
  rotation: MotionValue<number>
  shakeX: MotionValue<number>
  shakeY: MotionValue<number>
  contentScale: MotionValue<number>
  blurAmount: MotionValue<number>
  borderColor: MotionValue<string>
  borderOpacity: MotionValue<number>
  glowIntensity: MotionValue<number>
}

/** tiny util: stable init once without eslint disables */
function useConst<T>(init: () => T): T {
  const ref = useRef<T | null>(null)
  if (ref.current === null) ref.current = init()
  return ref.current
}

/** minimal, SSR-safe reduced-motion hook */
function usePrefersReducedMotion() {
  const [prefers, setPrefers] = useState(false)
  useEffect(() => {
    if (typeof window === "undefined" || !("matchMedia" in window)) return
    const mql = window.matchMedia("(prefers-reduced-motion: reduce)")
    const set = () => setPrefers(mql.matches)
    set()
    // legacy Safari support
    const onChange = (e: MediaQueryListEvent) => setPrefers(e.matches)
    mql.addEventListener ? mql.addEventListener("change", onChange) : mql.addListener(onChange)
    return () => {
      mql.removeEventListener
        ? mql.removeEventListener("change", onChange)
        : mql.removeListener(onChange)
    }
  }, [])
  return prefers
}

// ------------------------
// useEffectMotion
// ------------------------
export function useEffectMotion(): EffectMotionValues {
  const nodeWidth = useSpring(dimensions.node.width, springs.nodeWidth)
  const contentOpacity = useSpring(1, springs.default)
  const flashOpacity = useMotionValue(0)
  const flashColor = useMotionValue<string>(colors.flash)
  const borderRadius = useSpring(theme.radius.md, springs.default)
  const nodeHeight = useMotionValue(64)
  const rotation = useMotionValue(0)
  const shakeX = useMotionValue(0)
  const shakeY = useMotionValue(0)
  const contentScale = useSpring(1, springs.default)
  const borderColor = useMotionValue<string>(colors.border.default)
  const borderOpacity = useSpring(1, springs.default)
  const glowIntensity = useSpring(0, springs.default)

  const rotationVelocity = useVelocity(rotation)

  // Optional smoothing of velocity -> blur to avoid flicker
  // const smoothedVel = useSpring(rotationVelocity, { stiffness: 200, damping: 40 })
  const blurAmount = useTransform(rotationVelocity, [-100, 0, 100], [1, 0, 1], { clamp: true })

  // Stable container without eslint disables
  const motionValues = useConst<EffectMotionValues>(() => ({
    nodeWidth,
    nodeHeight,
    contentOpacity,
    flashOpacity,
    flashColor,
    borderRadius,
    rotation,
    shakeX,
    shakeY,
    contentScale,
    blurAmount,
    borderColor,
    borderOpacity,
    glowIntensity,
  }))

  return motionValues
}

// ------------------------
// useRunningAnimation
// ------------------------
export function useRunningAnimation(
  isRunning: boolean,
  motionValues: Pick<
    EffectMotionValues,
    "rotation" | "shakeX" | "shakeY" | "borderOpacity" | "glowIntensity"
  >,
) {
  const prefersReducedMotion = usePrefersReducedMotion()

  useEffect(() => {
    let cancelled = false
    let rafId: number | null = null // single RAF id (fix leak)
    const animControls: {
      border: AnimationPlaybackControls | undefined
      glow: AnimationPlaybackControls | undefined
    } = { border: undefined, glow: undefined }

    const stopAll = () => {
      if (animControls.border) animControls.border.stop()
      if (animControls.glow) animControls.glow.stop()
      if (rafId !== null) cancelAnimationFrame(rafId)
      // reset quickly
      animate(motionValues.rotation, 0, {
        duration: timing.exit.duration,
        ease: timing.exit.ease,
      })
      animate(motionValues.shakeX, 0, {
        duration: timing.exit.duration,
        ease: timing.exit.ease,
      })
      animate(motionValues.shakeY, 0, {
        duration: timing.exit.duration,
        ease: timing.exit.ease,
      })
      motionValues.borderOpacity.set(1)
      motionValues.glowIntensity.set(0)
    }

    if (!isRunning || prefersReducedMotion) {
      stopAll()
      return
    }

    // border pulse
    animControls.border = animate(motionValues.borderOpacity, [...timing.borderPulse.values], {
      duration: timing.borderPulse.duration,
      ease: "easeInOut",
      repeat: Infinity,
    })

    // glow pulse
    animControls.glow = animate(motionValues.glowIntensity, [...timing.glowPulse.values], {
      duration: timing.glowPulse.duration,
      ease: "easeInOut",
      repeat: Infinity,
    })

    const jitter = () => {
      if (cancelled) return

      const angle =
        (Math.random() * shake.running.angleRange + shake.running.angleBase) *
        (Math.random() < 0.5 ? 1 : -1)

      const offset =
        (Math.random() * shake.running.offsetRange + shake.running.offsetBase) *
        (Math.random() < 0.5 ? -1 : 1)

      const offsetY =
        (Math.random() * shake.running.offsetYRange + shake.running.offsetYBase) *
        (Math.random() < 0.5 ? -1 : 1)

      // FIX: proper [min,max] duration
      const min = shake.running.durationMin
      const max = shake.running.durationMax ?? min * 2
      const duration = min + Math.random() * Math.max(0.001, max - min)

      const rot = animate(motionValues.rotation, angle, { duration, ease: "circInOut" })
      const x = animate(motionValues.shakeX, offset, { duration, ease: "easeInOut" })
      const y = animate(motionValues.shakeY, offsetY, { duration, ease: "easeInOut" })

      // When this triple finishes, schedule next cycle on next frame
      Promise.all([rot.finished, x.finished, y.finished]).then(() => {
        if (cancelled) return
        rafId = requestAnimationFrame(jitter)
      })
    }

    rafId = requestAnimationFrame(jitter)

    return () => {
      cancelled = true
      stopAll()
    }
  }, [
    isRunning,
    motionValues.rotation,
    motionValues.shakeX,
    motionValues.shakeY,
    motionValues.borderOpacity,
    motionValues.glowIntensity,
  ])
}

// ------------------------
// useStateAnimations
// ------------------------
export function useStateAnimations(state: EffectState, motionValues: EffectMotionValues) {
  const isRunning = state.type === "running"

  // Radius
  useEffect(() => {
    motionValues.borderRadius.set(isRunning ? 15 : theme.radius.md)
  }, [isRunning, motionValues.borderRadius])

  // Height
  useEffect(() => {
    animate(motionValues.nodeHeight, isRunning ? 64 * 0.4 : 64, {
      duration: 0.4,
      bounce: isRunning ? 0.3 : 0.5,
      type: "spring",
    })
  }, [isRunning, motionValues.nodeHeight])

  // Width & content opacity
  useEffect(() => {
    const hasResult = state.type === "completed"

    if (!hasResult) {
      motionValues.nodeWidth.set(64) // if you want this animated, swap to animate(...)
    }

    motionValues.contentOpacity.set(hasResult ? 1 : state.type === "running" ? 0 : 1)
  }, [state, motionValues, isRunning])
}

// ------------------------
// useEffectAnimations
// ------------------------
export function useEffectAnimations(
  state: EffectState,
  motionValues: EffectMotionValues,
  isHovering: boolean,
  setShowErrorBubble: (show: boolean) => void,
) {
  const prefersReducedMotion = usePrefersReducedMotion()
  const isFailish = state.type === "failed" || state.type === "death"

  // Error bubble visibility (comment aligned with code: 1.5s)
  useEffect(() => {
    let timer: ReturnType<typeof setTimeout> | undefined

    if (isFailish) {
      setShowErrorBubble(true)
      timer = setTimeout(() => {
        if (!isHovering) setShowErrorBubble(false)
      }, 1500)
    } else {
      setShowErrorBubble(false)
    }

    return () => {
      if (timer) clearTimeout(timer)
    }
  }, [isFailish, isHovering, setShowErrorBubble])

  // Flash on start/complete
  const transition = useStateTransition(state)
  useEffect(() => {
    if (transition.justCompleted || transition.justStarted) {
      const up = animate(motionValues.flashOpacity, 0.6, {
        duration: 0.02,
        ease: "circOut",
      })
      up.finished.then(() => {
        if (prefersReducedMotion) {
          motionValues.flashOpacity.set(0)
        } else {
          animate(motionValues.flashOpacity, 0, {
            duration: timing.flash.duration,
            ease: timing.flash.ease,
          })
        }
      })
    }
  }, [
    transition.justCompleted,
    transition.justStarted,
    motionValues.flashOpacity,
    prefersReducedMotion,
  ])

  // Failure/Death shake
  useEffect(() => {
    if (!(state.type === "failed" || state.type === "death") || prefersReducedMotion) return

    let cancelled = false

    const shakeSequence = async () => {
      const { intensity, duration, count, rotationRange, returnDuration } = shake.failure

      for (let i = 0; i < count && !cancelled; i++) {
        const xOffset = (Math.random() - 0.5) * intensity
        const yOffset = (Math.random() - 0.5) * intensity
        const rotOffset = (Math.random() - 0.5) * rotationRange

        const anims = [
          animate(motionValues.shakeX, xOffset, { duration, ease: "easeInOut" }),
          animate(motionValues.shakeY, yOffset, { duration, ease: "easeInOut" }),
          animate(motionValues.rotation, rotOffset, { duration, ease: "easeInOut" }),
        ]
        await Promise.all(anims.map(a => a.finished))
      }

      if (!cancelled) {
        await Promise.all([
          animate(motionValues.shakeX, 0, { duration: returnDuration, ease: "easeOut" }).finished,
          animate(motionValues.shakeY, 0, { duration: returnDuration, ease: "easeOut" }).finished,
          animate(motionValues.rotation, 0, { duration: returnDuration, ease: "easeOut" }).finished,
        ])
      }
    }

    shakeSequence()
    return () => {
      cancelled = true
    }
  }, [state, prefersReducedMotion, motionValues.shakeX, motionValues.shakeY, motionValues.rotation])

  // Death glitch
  useEffect(() => {
    if (state.type !== "death" || prefersReducedMotion) {
      motionValues.glowIntensity.set(0)
      return
    }

    let cancelled = false
    let timeoutId: ReturnType<typeof setTimeout> | null = null

    const scheduleIdle = (cb: () => void, delay: number) => {
      timeoutId = setTimeout(() => {
        // requestIdleCallback if available
        const win = window as Window & { requestIdleCallback?: (cb: () => void) => number }
        if (typeof win.requestIdleCallback === "function") {
          win.requestIdleCallback(cb)
        } else {
          cb()
        }
      }, delay)
    }

    const glitchSequence = async () => {
      const t = timing.glitch
      const e = effects.glitch

      // initial pulses
      for (let i = 0; i < t.initialCount && !cancelled; i++) {
        motionValues.contentScale.set(1 + Math.random() * e.scaleRange)
        motionValues.glowIntensity.set(Math.random() * e.intensePulseMax)

        await new Promise<void>(resolve => {
          scheduleIdle(
            resolve,
            t.initialDelayMin + Math.random() * Math.max(0, t.initialDelayMax - t.initialDelayMin),
          )
        })

        if (cancelled) break
        motionValues.contentScale.set(1)
        motionValues.glowIntensity.set(e.glowMax)

        await new Promise<void>(resolve => {
          scheduleIdle(resolve, t.pauseMin + Math.random() * Math.max(0, t.pauseMax - t.pauseMin))
        })
      }

      // subtle loop (only one timeout pending at any time)
      const subtle = () => {
        if (cancelled) return
        motionValues.glowIntensity.set(e.glowMin + Math.random() * (e.glowMax - e.glowMin))
        scheduleIdle(
          subtle,
          t.subtleDelayMin + Math.random() * Math.max(0, t.subtleDelayMax - t.subtleDelayMin),
        )
      }
      subtle()
    }

    glitchSequence()

    return () => {
      cancelled = true
      if (timeoutId) clearTimeout(timeoutId)
      motionValues.glowIntensity.set(0)
    }
  }, [state, prefersReducedMotion, motionValues.contentScale, motionValues.glowIntensity])

  // Content scale pop on completion
  useLayoutEffect(() => {
    if (transition.justCompleted) {
      motionValues.contentScale.set(0)
      animate(motionValues.contentScale, [1.3, 1], springs.contentScale)
    }
  }, [transition.justCompleted, motionValues.contentScale])
}


================================================
FILE: src/components/feedback/DeathBubble.tsx
================================================
"use client"

import { animate, motion, useMotionValue, useTransform } from "motion/react"
import { useEffect, useMemo } from "react"
import { colors, shake, springs } from "@/animations"
import { dimensions } from "@/constants/dimensions"

interface DeathBubbleProps {
  error: unknown
}

export function DeathBubble({ error }: DeathBubbleProps) {
  // Generate glitchy characters instead of showing the real message
  const glitchChars = useMemo(() => {
    const source = error instanceof Error ? error.message : String(error ?? "DEAD")
    const charset = "@#$%&*/=-+?!~<>\\|█▓▒░"
    return Array.from({ length: Math.max(6, Math.min(source.length, 16)) })
      .map(() => charset[Math.floor(Math.random() * charset.length)])
      .join("")
  }, [error])

  const shakeX = useMotionValue(0)
  const shakeY = useMotionValue(0)
  const rotation = useMotionValue(0)

  // Re-use the same shake behaviour as FailureBubble
  useEffect(() => {
    let cancelled = false

    const shakeSequence = async () => {
      if (cancelled) return

      const shakeIntensity = shake.bubble.intensity
      const shakeDuration = shake.bubble.duration
      const shakeCount = shake.bubble.count

      for (let i = 0; i < shakeCount; i++) {
        if (cancelled) break

        const xOffset = (Math.random() - 0.5) * shakeIntensity
        const yOffset = (Math.random() - 0.5) * shakeIntensity + shake.bubble.yOffset
        const rotOffset = (Math.random() - 0.5) * shake.bubble.rotationRange

        await Promise.all([
          animate(shakeX, xOffset, {
            duration: shakeDuration,
            ease: "easeInOut",
          }).finished,
          animate(shakeY, yOffset, {
            duration: shakeDuration,
            ease: "easeInOut",
          }).finished,
          animate(rotation, rotOffset, {
            duration: shakeDuration,
            ease: "easeInOut",
          }).finished,
        ])

        if (cancelled) break
      }

      if (!cancelled) {
        await Promise.all([
          animate(shakeX, 0, {
            duration: shake.bubble.returnDuration,
            ease: "easeOut",
          }).finished,
          animate(shakeY, shake.bubble.yOffset, {
            duration: shake.bubble.returnDuration,
            ease: "easeOut",
          }).finished,
          animate(rotation, 0, {
            duration: shake.bubble.returnDuration,
            ease: "easeOut",
          }).finished,
        ])
      }
    }

    const timer = setTimeout(shakeSequence, shake.bubble.delay)

    return () => {
      cancelled = true
      clearTimeout(timer)
    }
  }, [shakeX, shakeY, rotation])

  const background = "rgba(0,0,0,0.95)" // black background
  const arrowColor = "rgba(0,0,0,0.95)"

  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.8, y: 20, filter: "blur(5px)" }}
      animate={{ opacity: 1, scale: 1, y: -5, filter: "blur(0px)" }}
      exit={{ opacity: 0, scale: 0.8, y: 20, filter: "blur(5px)" }}
      transition={springs.failureBubble}
      style={{
        position: "absolute",
        bottom: "100%",
        left: "50%",
        marginBottom: "8px",
        x: useTransform([shakeX], ([x]) => `calc(-50% + ${x}px)`),
        y: shakeY,
        zIndex: 10,
        rotate: rotation,
      }}
    >
      <div
        className="text-sm p-1 px-2 font-bold"
        style={{
          background,
          color: "#dc2626", // red text
          borderRadius: dimensions.failureBubble.borderRadius,
          whiteSpace: "nowrap",
          maxWidth: dimensions.failureBubble.maxWidth,
          boxShadow: colors.failureBubble.shadow,
          border: "1px solid rgba(220,38,38,0.9)",
        }}
      >
        {glitchChars}
      </div>
      <div
        style={{
          position: "absolute",
          top: "100%",
          left: "50%",
          transform: "translateX(-50%)",
          width: 0,
          height: 0,
          borderLeft: `${dimensions.failureBubble.arrowSize} solid transparent`,
          borderRight: `${dimensions.failureBubble.arrowSize} solid transparent`,
          borderTop: `${dimensions.failureBubble.arrowSize} solid ${arrowColor}`,
        }}
      />
    </motion.div>
  )
}


================================================
FILE: src/components/feedback/EffectLogo.tsx
================================================
import { motion, type Transition } from "motion/react"

export function EffectLogo({ className = "h-7" }: { className?: string }) {
  // Base animation keyframes for the pulsing / glowing effect
  const pulseKeyframes = {
    scale: [1],
    opacity: [1],
    y: [0], // subtle vertical motion (up then back)
  }

  const baseTransition: Transition = {
    duration: 2,
    ease: "easeInOut" as const,
    repeat: Infinity,
  }

  return (
    <svg
      viewBox="0 0 32 32"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className={className}
      style={{ overflow: "visible" }}
    >
      {/* Layer 1 */}
      <motion.path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M29.8022 24.317C30.2747 24.05 30.4361 23.4582 30.1636 22.9953C29.891 22.5329 29.2873 22.3741 28.8148 22.6411L15.9211 29.9362L3.07463 22.6683C2.60281 22.4012 1.999 22.5594 1.72597 23.0225C1.45347 23.4854 1.61541 24.077 2.08741 24.3441L15.3897 31.8698C15.5053 31.9353 15.6327 31.9771 15.7645 31.9929C15.8963 32.0087 16.0299 31.9981 16.1576 31.9617C16.278 31.9433 16.3941 31.9031 16.5002 31.8431L29.8022 24.317Z"
        fill="white"
        style={{
          transformOrigin: "50% 50%",
          filter: "drop-shadow(0 0 4px rgba(59,130,246,0.4))",
        }}
        animate={pulseKeyframes}
        transition={{ ...baseTransition, delay: 0 }}
      />

      {/* Layer 2 */}
      <motion.path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M31.1298 16.6012C31.1974 16.1929 31.0061 15.7675 30.6177 15.5488L16.555 7.63105C16.4443 7.56873 16.3234 7.52682 16.198 7.50732C16.0631 7.46888 15.922 7.45758 15.7827 7.47405C15.6434 7.49053 15.5088 7.53446 15.3865 7.60332L1.32289 15.5214C0.913972 15.7518 0.723686 16.2117 0.824499 16.6391C0.780205 16.9913 0.91787 17.3598 1.32768 17.5916L15.3904 25.5478C15.5127 25.6169 15.6475 25.661 15.7869 25.6776C15.9263 25.6942 16.0675 25.6829 16.2026 25.6445C16.3297 25.6253 16.4522 25.583 16.5642 25.5197L30.6275 17.563C31.0408 17.329 31.1776 16.9562 31.1298 16.6012ZM28.2266 16.5591L15.9459 9.64453L3.67206 16.5554L15.9528 23.5034L28.2266 16.5591Z"
        fill="white"
        style={{
          transformOrigin: "50% 50%",
          filter: "drop-shadow(0 0 4px rgba(59,130,246,0.4))",
        }}
        animate={pulseKeyframes}
        transition={{ ...baseTransition, delay: 0.1 }}
      />

      {/* Top filled + outline group */}
      <motion.g
        animate={pulseKeyframes}
        transition={{ ...baseTransition, delay: 0.2 }}
        style={{ transformOrigin: "50% 50%" }}
      >
        {/* Filled top shape */}
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M31.3429 10.6097C31.8677 10.3131 32.0476 9.65608 31.7442 9.14178C31.4416 8.62819 30.7712 8.45201 30.2464 8.74854L15.9269 16.8501L1.66063 8.77876C1.13584 8.48152 0.465408 8.65787 0.162793 9.172C-0.14053 9.68541 0.0391253 10.3432 0.564095 10.6397L15.337 18.9976C15.4654 19.0702 15.607 19.1165 15.7534 19.1339C15.8998 19.1514 16.0482 19.1395 16.19 19.0991C16.3236 19.0791 16.4524 19.0347 16.5701 18.9681L31.3429 10.6097Z"
          fill="white"
          style={{
            filter: "drop-shadow(0 0 4px rgba(59,130,246,0.4))",
          }}
        />

        {/* Outline top shape */}
        <path
          fillRule="evenodd"
          clipRule="evenodd"
          d="M31.3255 8.49027C31.8513 8.78627 32.0333 9.44279 31.7325 9.95692C31.4307 10.4707 30.7603 10.6474 30.2344 10.3514L15.9128 2.28787L1.64317 10.3224C1.11731 10.6184 0.44688 10.4415 0.145328 9.92794C-0.15587 9.41381 0.0262664 8.75729 0.55159 8.46129L15.325 0.143725C15.4534 0.0713274 15.5949 0.0251207 15.7412 0.00776107C15.8875 -0.00959854 16.0358 0.00223134 16.1775 0.0425706C16.3093 0.0631109 16.4364 0.107185 16.5526 0.172702L31.3255 8.49027Z"
          fill="white"
          style={{
            filter: "drop-shadow(0 0 4px rgba(59,130,246,0.4))",
          }}
        />

        <path
          d="M2.7403 9.6795L15.8991 1.62024L29.0577 9.67879L15.8989 17.2013L2.7403 9.6795Z"
          fill="white"
          style={{
            transformOrigin: "50% 50%",
            filter: "drop-shadow(0 0 4px rgba(59,130,246,0.4))",
          }}
        />
      </motion.g>

      {/* (Former Layer 5) is now part of the grouped animation above */}
    </svg>
  )
}


================================================
FILE: src/components/feedback/FailureBubble.tsx
================================================
"use client"

import { animate, motion, useMotionValue, useTransform } from "motion/react"
import { useEffect } from "react"
import { colors, shake, springs } from "@/animations"
import { dimensions } from "@/constants/dimensions"

interface FailureBubbleProps {
  error: unknown
}

export function FailureBubble({ error }: FailureBubbleProps) {
  const errorMessage = error instanceof Error ? error.message : String(error)
  const shakeX = useMotionValue(0)
  const shakeY = useMotionValue(0)
  const rotation = useMotionValue(0)

  // Shake animation when bubble appears
  useEffect(() => {
    let cancelled = false
    const shakeSequence = async () => {
      if (cancelled) return

      // Shake animation - similar to error node but gentler
      const shakeIntensity = shake.bubble.intensity
      const shakeDuration = shake.bubble.duration
      const shakeCount = shake.bubble.count

      for (let i = 0; i < shakeCount; i++) {
        if (cancelled) break

        const xOffset = (Math.random() - 0.5) * shakeIntensity
        const yOffset = (Math.random() - 0.5) * shakeIntensity + shake.bubble.yOffset
        const rotOffset = (Math.random() - 0.5) * shake.bubble.rotationRange

        await Promise.all([
          animate(shakeX, xOffset, {
            duration: shakeDuration,
            ease: "easeInOut",
          }).finished,
          animate(shakeY, yOffset, {
            duration: shakeDuration,
            ease: "easeInOut",
          }).finished,
          animate(rotation, rotOffset, {
            duration: shakeDuration,
            ease: "easeInOut",
          }).finished,
        ])

        if (cancelled) break
      }

      // Return to rest position
      if (!cancelled) {
        await Promise.all([
          animate(shakeX, 0, {
            duration: shake.bubble.returnDuration,
            ease: "easeOut",
          }).finished,
          animate(shakeY, shake.bubble.yOffset, {
            duration: shake.bubble.returnDuration,
            ease: "easeOut",
          }).finished,
          animate(rotation, 0, {
            duration: shake.bubble.returnDuration,
            ease: "easeOut",
          }).finished,
        ])
      }
    }

    // Start shake after a brief delay to let the bubble appear first
    const timer = setTimeout(shakeSequence, shake.bubble.delay)

    return () => {
      cancelled = true
      clearTimeout(timer)
    }
  }, [shakeX, shakeY, rotation])

  return (
    <motion.div
      initial={{
        opacity: 0,
        scale: 0.8,
        y: 20,
        filter: "blur(5px)",
      }}
      animate={{
        opacity: 1,
        scale: 1,
        y: -5,
        filter: "blur(0px)",
      }}
      exit={{
        opacity: 0,
        scale: 0.8,
        y: 20,
        filter: "blur(5px)",
      }}
      transition={springs.failureBubble}
      style={{
        position: "absolute",
        bottom: "100%",
        left: "50%",
        marginBottom: "8px",
        x: useTransform([shakeX], ([x]) => `calc(-50% + ${x}px)`),
        y: shakeY,
        zIndex: 10,
        rotate: rotation,
      }}
    >
      <div
        className={`text-sm p-1 px-2 ${colors.failureBubble.text} font-bold`}
        style={{
          background: colors.failureBubble.background,
          borderRadius: dimensions.failureBubble.borderRadius,
          whiteSpace: "nowrap",
          maxWidth: dimensions.failureBubble.maxWidth,
          boxShadow: colors.failureBubble.shadow,
        }}
      >
        {errorMessage}
      </div>
      {/* Arrow pointing down */}
      <div
        style={{
          position: "absolute",
          top: "100%",
          left: "50%",
          transform: "translateX(-50%)",
          width: 0,
          height: 0,
          borderLeft: `${dimensions.failureBubble.arrowSize} solid transparent`,
          borderRight: `${dimensions.failureBubble.arrowSize} solid transparent`,
          borderTop: `${dimensions.failureBubble.arrowSize} solid ${colors.failureBubble.background}`,
        }}
      />
    </motion.div>
  )
}


================================================
FILE: src/components/feedback/FloatingHighlight.tsx
================================================
import { animate, motion, useMotionValue, useSpring, useTransform } from "motion/react"
import { useEffect, useRef } from "react"

interface HighlightTarget {
  text: string
}

interface FloatingHighlightProps {
  containerRef: React.RefObject<HTMLDivElement>
  target: HighlightTarget | null
}

export const FloatingHighlight: React.FC<FloatingHighlightProps> = ({ containerRef, target }) => {
  // Snappy spring animations for position and size
  const springConfig = { bounce: 0.0, visualDuration: 0.2 }
  const x = useSpring(0, springConfig)
  const y = useSpring(0, springConfig)
  const width = useSpring(0, springConfig)
  const height = useSpring(0, springConfig)
  const opacity = useMotionValue(0)
  const scale = useSpring(1, { stiffness: 300, damping: 20 })
  const highlightRef = useRef<HTMLDivElement>(null)

  // Map opacity to blur: 0 opacity = 4px blur, 1 opacity = 0px blur
  const blur = useTransform(opacity, [0, 1], [4, 0])

  useEffect(() => {
    if (!containerRef.current || !target) {
      animate(opacity, 0, { ease: "linear" })
      scale.set(0.9)
      return
    }

    // Use requestAnimationFrame to ensure DOM is ready
    const rafId = requestAnimationFrame(() => {
      if (!containerRef.current) return

      // Get the pre element inside the container
      const preElement = containerRef.current.querySelector("pre")
      if (!preElement) {
        animate(opacity, 0, { ease: "linear" })
        scale.set(0.95)
        return
      }

      // Get all text content and build a map of character positions
      const textNodes: Array<{ node: Node; start: number; text: string }> = []
      let totalLength = 0

      // Walk through all text nodes in the pre element
      const walker = document.createTreeWalker(preElement, NodeFilter.SHOW_TEXT, null)

      let node: Node | null
      // biome-ignore lint/suspicious/noAssignInExpressions: because I don't care.
      while ((node = walker.nextNode())) {
        const text = node.textContent || ""
        textNodes.push({ node, start: totalLength, text })
        totalLength += text.length
      }

      // Get the full text content
      const fullText = textNodes.map(n => n.text).join("")

      // Find the target text in the full content
      const targetIndex = fullText.indexOf(target.text)

      if (targetIndex === -1) {
        animate(opacity, 0, { ease: "linear" })
        scale.set(0.95)
        return
      }

      // Find which nodes contain the start and end of the target
      const targetEnd = targetIndex + target.text.length
      let startNode: Node | null = null
      let startOffset = 0
      let endNode: Node | null = null
      let endOffset = 0

      for (const { node, start, text } of textNodes) {
        const nodeEnd = start + text.length

        // Check if target starts in this node
        if (!startNode && targetIndex >= start && targetIndex < nodeEnd) {
          startNode = node
          startOffset = targetIndex - start
        }

        // Check if target ends in this node
        if (!endNode && targetEnd > start && targetEnd <= nodeEnd) {
          endNode = node
          endOffset = targetEnd - start
        }

        // If we found both, we can stop
        if (startNode && endNode) break
      }

      if (startNode && endNode) {
        try {
          const range = document.createRange()
          range.setStart(startNode, startOffset)
          range.setEnd(endNode, endOffset)

          // Get all client rects (in case text spans multiple lines)
          const rects = Array.from(range.getClientRects())

          if (rects.length > 0) {
            const firstRect = rects[0]
            if (!firstRect) return

            // Calculate bounding box
            const bounds = rects.reduce(
              (acc, rect) => ({
                left: Math.min(acc.left, rect.left),
                top: Math.min(acc.top, rect.top),
                right: Math.max(acc.right, rect.right),
                bottom: Math.max(acc.bottom, rect.bottom),
              }),
              {
                left: firstRect.left,
                top: firstRect.top,
                right: firstRect.right,
                bottom: firstRect.bottom,
              },
            )

            // Get container position
            const containerRect = containerRef.current.getBoundingClientRect()

            // Calculate relative position
            const relX = bounds.left - containerRect.left
            const relY = bounds.top - containerRect.top
            const relWidth = bounds.right - bounds.left
            const relHeight = bounds.bottom - bounds.top

            // Apply with padding
            const paddingX = 8
            const paddingY = 6
            x.set(relX - paddingX)
            y.set(relY - paddingY)
            width.set(relWidth + paddingX * 2)
            height.set(relHeight + paddingY * 2)
            animate(opacity, 1, { ease: "linear" })
            scale.set(1)
          }
        } catch (e) {
          console.error("Error creating range:", e)
          animate(opacity, 0, { ease: "linear" })
          scale.set(0.95)
        }
      } else {
        animate(opacity, 0, { ease: "linear" })
        scale.set(0.95)
      }
    })

    return () => cancelAnimationFrame(rafId)
  }, [target, containerRef, x, y, width, height, opacity, scale])

  return (
    <motion.div
      ref={highlightRef}
      style={{
        position: "absolute",
        left: 0,
        top: 0,
        x,
        y,
        width,
        height,
        opacity,
        scale,
        borderRadius: 6,
        background: "rgba(56, 189, 248, 0.15)",
        border: `1px solid rgba(56, 189, 248, 0.6)`,
        boxShadow: `0 0 10px rgba(56, 189, 248, 0.3)`,
        pointerEvents: "none",
        zIndex: 10,
        filter: useTransform(blur, v => `blur(${Math.max(0, v)}px)`),
      }}
    />
  )
}


================================================
FILE: src/components/feedback/NotificationBubble.tsx
================================================
"use client"

import { animate, motion, useMotionValue, useTransform } from "motion/react"
import { useEffect } from "react"
import { taskSounds } from "@/sounds/TaskSounds"
import type { Notification } from "@/VisualEffect"

interface NotificationBubbleProps {
  notification: Notification
}

export function NotificationBubble({ notification }: NotificationBubbleProps) {
  const floatY = useMotionValue(0)

  // Play notification chime when notification appears
  useEffect(() => {
    taskSounds.playNotificationChime()
  }, []) // Only play once when component mounts

  // Gentle floating animation
  useEffect(() => {
    let cancelled = false

    const floatSequence = async () => {
      if (cancelled) return
      // Faster floating up and down with ease in out
      while (!cancelled) {
        await animate(floatY, -12, {
          duration: 0.8,
          ease: "easeInOut",
        }).finished

        if (cancelled) break

        await animate(floatY, 0, {
          duration: 0.8,
          ease: "easeInOut",
        }).finished
      }
    }

    floatSequence()

    return () => {
      cancelled = true
    }
  }, [floatY])

  const transform = useTransform([floatY], ([y]) => `translateY(${y}px)`)

  return (
    <motion.div
      style={{
        position: "absolute",
        bottom: "100%",
        left: "50%",
        x: "-50%",
        zIndex: 1000,
        pointerEvents: "none",
      }}
      initial={{ opacity: 0, scale: 0, y: 50, filter: "blur(10px)" }}
      animate={{ opacity: 1, scale: 1, y: 0, filter: "blur(0px)" }}
      exit={{ opacity: 0, scale: 0, y: 50, filter: "blur(10px)" }}
      transition={{
        type: "spring",
        visualDuration: 0.3,
        bounce: 0.1,
      }}
    >
      <motion.div
        style={{
          transform,
        }}
      >
        {/* Arrow pointing down */}
        <div
          style={{
            position: "absolute",
            bottom: -7,
            left: "50%",
            transform: "translateX(-50%)",
            width: 0,
            height: 0,
            borderLeft: "8px solid transparent",
            borderRight: "8px solid transparent",
            borderTop: "8px solid #3b82f6", // Blue background
          }}
        />

        {/* Notification content */}
        <div
          className="text-xl p-2 px-3 text-white font-medium flex items-center gap-2"
          style={{
            background: "#3b82f6", // Blue background
            borderRadius: "8px",
            maxWidth: "200px",
            boxShadow: "0 8px 32px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(59, 130, 246, 0.3)",
          }}
        >
          {notification.icon && <span className="text-lg">{notification.icon}</span>}
          <span>{notification.message}</span>
        </div>
      </motion.div>
    </motion.div>
  )
}


================================================
FILE: src/components/feedback/index.ts
================================================
export { DeathBubble } from "./DeathBubble"
export { EffectLogo } from "./EffectLogo"
export { FailureBubble } from "./FailureBubble"
export { FloatingHighlight } from "./FloatingHighlight"
export { NotificationBubble } from "./NotificationBubble"


================================================
FILE: src/components/index.ts
================================================
// Content components
export * from "./CodeBlock"
// Display components
export * from "./display"
// Effect visualization
export * from "./effect"
// Feedback components
export * from "./feedback"
export * from "./HeaderView"
// Renderers
export * from "./renderers"
// UI components
export * from "./ui"


================================================
FILE: src/components/layout/NavigationSidebar.tsx
================================================
import { HashStraightIcon } from "@phosphor-icons/react"
import { memo, useMemo } from "react"

interface Example {
  id: string
  name: string
  variant?: string
  section?: string
}

interface NavigationSidebarProps {
  examples: Array<Example>
  currentExample?: string | undefined
  onExampleSelect: (id: string) => void
}

function NavigationSidebarComponent({
  currentExample,
  examples,
  onExampleSelect,
}: NavigationSidebarProps) {
  // Group examples by section - memoize to avoid recomputation
  const sections = useMemo(
    () =>
      examples.reduce(
        (acc, example) => {
          const section = example.section || "Other"
          if (!acc[section]) {
            acc[section] = []
          }
          acc[section].push(example)
          return acc
        },
        {} as Record<string, Array<Example>>,
      ),
    [examples],
  )

  return (
    <aside
      className="fixed top-0 w-76 h-screen z-40 hidden xl:block"
      style={{ left: "max(0px, calc(50vw - 40rem))" }}
    >
      <div className="h-full overflow-y-auto border-r border-neutral-800 sidebar-scrollbar">
        <div className="p-6">
          <div className="mb-6">
            <h2 className="text-sm font-mono text-neutral-500 mb-4">EXAMPLES</h2>
          </div>

          <div>
            {Object.entries(sections).map(([sectionName, sectionExamples]) => (
              <div key={sectionName} className="mb-8">
                <h3 className="text-xs font-mono text-neutral-600 mb-3 font-bold tracking-wider">
                  <span className="flex items-center gap-1">
                    <HashStraightIcon size={14} />
                    {sectionName.toUpperCase()}
                  </span>
                </h3>
                <nav className="space-y-1">
                  {sectionExamples.map(example => {
                    const isActive = currentExample === example.id

                    return (
                      <button
                        type="button"
                        key={example.id}
                        onClick={() => onExampleSelect(example.id)}
                        className={`
                          w-full text-left py-1 px-2 -mx-2 text-sm font-mono cursor-pointer rounded-md 
                          ${
                            isActive
                              ? "text-white"
                              : "text-neutral-400 hover:text-neutral-200 hover:bg-white/5"
                          }
                          focus:outline-none
                        `}
                      >
                        <span className="flex items-baseline gap-1.5">
                          <span>{example.name}</span>
                          {example.variant && (
                            <span className="text-xs text-neutral-500">{example.variant}</span>
                          )}
                        </span>
                      </button>
                    )
                  })}
                </nav>
              </div>
            ))}
          </div>
        </div>
      </div>
    </aside>
  )
}

// Memoize the component to prevent re-renders when props don't change
export const NavigationSidebar = memo(NavigationSidebarComponent)


================================================
FILE: src/components/layout/PageHeader.tsx
================================================
import { StarFourIcon } from "@phosphor-icons/react"
import { motion, useAnimationFrame, useMotionValue, useTransform, useVelocity } from "motion/react"
import { useRouter } from "next/navigation"
import { useRef, useState } from "react"
import { VolumeToggle } from "@/components/ui"

interface HeaderProps {
  isMuted: boolean
  onMuteToggle: () => void
}

export function PageHeader({ isMuted, onMuteToggle }: HeaderProps) {
  const [isHovering, setIsHovering] = useState(false)
  const rotation = useMotionValue(0)
  const velocity = useRef(770)
  const lastTime = useRef(performance.now())
  const router = useRouter()

  // Use velocity hook to track rotation velocity
  const rotationVelocity = useVelocity(rotation)

  // Map velocity to opacity: 0 deg/s = 0.6 opacity, 1200 deg/s = 1.0 opacity
  const opacity = useTransform(rotationVelocity, [0, 1200], [0.6, 1.0])

  useAnimationFrame(time => {
    const deltaTime = (time - lastTime.current) / 1000 // Convert to seconds
    lastTime.current = time
    const currentRotation = rotation.get()

    if (isHovering) {
      // Accelerate
      velocity.current = Math.min(velocity.current + 800 * deltaTime, 1200) // Max 1200 deg/s
      rotation.set(currentRotation + velocity.current * deltaTime)
    } else if (velocity.current > 0) {
      // Decelerate while still spinning
      velocity.current = Math.max(velocity.current - 400 * deltaTime, 0)
      rotation.set(currentRotation + velocity.current * deltaTime)
    } else if (Math.abs(currentRotation % 360) > 0.1) {
      // When stopped, smoothly return to neutral
      const normalizedRotation = currentRotation % 360
      const distanceToNeutral =
        normalizedRotation > 180 ? 360 - normalizedRotation : -normalizedRotation
      const returnSpeed = Math.min(Math.abs(distanceToNeutral) * 3, 200)
      const direction = distanceToNeutral > 0 ? 1 : -1

      rotation.set(currentRotation + direction * returnSpeed * deltaTime)

      // Snap when very close
      if (Math.abs(rotation.get() % 360) < 0.5) {
        rotation.set(Math.round(rotation.get() / 360) * 360)
      }
    }
  })

  return (
    <div className="self-start w-full flex items-start justify-between text-base">
      {/* Left side: Logo and title */}
      <button
        type="button"
        className="flex items-center gap-1.5 sm:gap-3 group select-none cursor-pointer"
        onMouseEnter={() => setIsHovering(true)}
        onMouseLeave={() => setIsHovering(false)}
        onClick={() => {
          // Use router.push for client-side navigation
          router.push("/")
        }}
      >
        <motion.div style={{ rotate: rotation, opacity }} className="flex items-center">
          <StarFourIcon size={16} weight="fill" />
        </motion.div>
        <span className="font-bold text-neutral-400 tracking-wide text-base">VISUAL EFFECT</span>
      </button>

      {/* Right side: Sound controls and credit */}
      <div className="flex items-end gap-1 flex-col">
        {/* Sound control */}
        <VolumeToggle isMuted={isMuted} onToggle={onMuteToggle} />
      </div>
    </div>
  )
}


================================================
FILE: src/components/renderers/ArrayResult.tsx
================================================
"use client"

import { motion } from "motion/react"
import type { RenderableResult } from "./RenderableResult"

export class ArrayResult<T> implements RenderableResult {
  constructor(public values: Array<T>) {}

  render() {
    return (
      <motion.span
        key="array"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        style={{ fontSize: 12 }}
      >
        [{this.values.length}]
      </motion.span>
    )
  }
}


================================================
FILE: src/components/renderers/BasicRenderers.tsx
================================================
import type { RenderableResult } from "./RenderableResult"

// Simple number renderer
export class NumberResult implements RenderableResult {
  constructor(public value: number) {}

  render() {
    return <div className="text-white font-mono text-xl">{this.value}</div>
  }
}

// Simple string renderer
export class StringResult implements RenderableResult {
  constructor(public value: string) {}

  render() {
    return <div className="text-white font-mono text-xl">{this.value}</div>
  }
}

// Boolean renderer
export class BooleanResult implements RenderableResult {
  constructor(public value: boolean) {}

  render() {
    return <div className="text-white font-mono">{this.value ? "true" : "false"}</div>
  }
}

// Object/JSON renderer
export class ObjectResult implements RenderableResult {
  constructor(public value: unknown) {}

  render() {
    return <div className="text-white font-mono text-xs">{JSON.stringify(this.value, null, 2)}</div>
  }
}


================================================
FILE: src/components/renderers/EmojiResult.tsx
================================================
import type { RenderableResult } from "./RenderableResult"

export class EmojiResult implements RenderableResult {
  constructor(public emoji: string) {}

  render() {
    return (
      <span key="emoji" className="text-4xl">
        {this.emoji}
      </span>
    )
  }
}


================================================
FILE: src/components/renderers/RenderableResult.ts
================================================
import type { ReactNode } from "react"

// Base interface for renderable results
export interface RenderableResult {
  render(): ReactNode
}

export interface RenderOptions {
  dimensions: {
    size: number
  }
}

// Type guard to check if a result is renderable
export function isRenderableResult(value: unknown): value is RenderableResult {
  return (
    value !== null &&
    typeof value === "object" &&
    "render" in value &&
    typeof (value as RenderableResult).render === "function"
  )
}

// Helper function to render any result
export function renderResult(result: unknown): ReactNode {
  if (isRenderableResult(result)) {
    return result.render()
  }

  // Default string rendering for non-renderable results
  return String(result)
}


================================================
FILE: src/components/renderers/TemperatureResult.tsx
================================================
import type { RenderableResult } from "./RenderableResult"

export class TemperatureResult implements RenderableResult {
  constructor(
    public value: number,
    public location?: string,
  ) {}

  render() {
    return (
      <div key="temp" className="text-xl">
        {this.value}°
      </div>
    )
  }
}

export class TemperatureArrayResult implements RenderableResult {
  constructor(public values: Array<number>) {}

  render() {
    return (
      <div key="array" className="text-xl">
        [{this.values.map(t => `${t}°`).join(", ")}]
      </div>
    )
  }
}


================================================
FILE: src/components/renderers/index.ts
================================================
export * from "./ArrayResult"
export * from "./BasicRenderers"
export * from "./EmojiResult"
export * from "./RenderableResult"
export * from "./TemperatureResult"


================================================
FILE: src/components/scope/FinalizerCard.tsx
================================================
import { AnimatePresence, motion } from "motion/react"
import { useEffect, useRef, useState } from "react"
import { springs } from "@/animations"
import type { Finalizer } from "../../VisualScope"

interface FinalizerCardProps {
  finalizer: Finalizer
}

export function FinalizerCard({ finalizer }: FinalizerCardProps) {
  const isRunning = finalizer.state === "running"
  const isCompleted = finalizer.state === "completed"

  // Track state transitions
  const prevStateRef = useRef(finalizer.state)
  const [justCompleted, setJustCompleted] = useState(false)

  useEffect(() => {
    if (prevStateRef.current !== "completed" && finalizer.state === "completed") {
      setJustCompleted(true)
      const timeout = setTimeout(() => setJustCompleted(false), 600) // Match animation duration
      return () => clearTimeout(timeout)
    }
    prevStateRef.current = finalizer.state
  }, [finalizer.state])

  return (
    <motion.div
      initial={{
        opacity: 0,
        scale: 1.2,
        filter: "blur(4px)",
      }}
      animate={{
        opacity: 1,
        scale: 1,
        filter: "blur(0px)",
      }}
      exit={{
        opacity: 0,
        scale: 0.8,
        filter: "blur(4px)",
      }}
      transition={{
        type: "spring",
        visualDuration: 0.3,
        bounce: 0.3,
      }}
      className={`relative flex items-center gap-3 px-4 py-3 rounded-lg h-[52px] shadow-lg shadow-neutral-900 transition-colors duration-200 ${
        finalizer.state === "pending"
          ? "bg-neutral-800 border border-neutral-700"
          : isRunning
            ? "bg-blue-900 border border-blue-500 "
            : "bg-green-900 border border-green-500"
      }`}
      style={{
        minWidth: "200px",
        willChange: "transform, opacity, filter",
        translateZ: 0,
      }}
    >
      {/* Checkbox container */}
      <motion.div
        initial={{ scale: 0.8, opacity: 0 }}
        animate={{ scale: 1, 
gitextract_q3jrgzjr/

├── .gitignore
├── CLAUDE.md
├── LICENSE
├── README.md
├── app/
│   ├── ClientAppContent.tsx
│   ├── [exampleId]/
│   │   └── page.tsx
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── biome.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.mjs
├── public/
│   ├── _headers
│   ├── generate-favicons.html
│   ├── robots.txt
│   ├── site.webmanifest
│   └── sitemap.xml
├── scripts/
│   └── generate-og-images.tsx
├── src/
│   ├── AppContent.tsx
│   ├── VisualEffect.test.ts
│   ├── VisualEffect.ts
│   ├── VisualRef.ts
│   ├── VisualScope.ts
│   ├── animations.ts
│   ├── components/
│   │   ├── CodeBlock.tsx
│   │   ├── HeaderView.tsx
│   │   ├── ScheduleTimeline.tsx
│   │   ├── Timer.tsx
│   │   ├── display/
│   │   │   ├── EffectExample.tsx
│   │   │   ├── RefDisplay.tsx
│   │   │   └── index.ts
│   │   ├── effect/
│   │   │   ├── EffectContainer.tsx
│   │   │   ├── EffectContent.tsx
│   │   │   ├── EffectLabel.tsx
│   │   │   ├── EffectNode.tsx
│   │   │   ├── EffectOverlay.tsx
│   │   │   ├── index.ts
│   │   │   ├── nodeVariants.ts
│   │   │   ├── taskUtils.ts
│   │   │   └── useEffectMotion.ts
│   │   ├── feedback/
│   │   │   ├── DeathBubble.tsx
│   │   │   ├── EffectLogo.tsx
│   │   │   ├── FailureBubble.tsx
│   │   │   ├── FloatingHighlight.tsx
│   │   │   ├── NotificationBubble.tsx
│   │   │   └── index.ts
│   │   ├── index.ts
│   │   ├── layout/
│   │   │   ├── NavigationSidebar.tsx
│   │   │   └── PageHeader.tsx
│   │   ├── renderers/
│   │   │   ├── ArrayResult.tsx
│   │   │   ├── BasicRenderers.tsx
│   │   │   ├── EmojiResult.tsx
│   │   │   ├── RenderableResult.ts
│   │   │   ├── TemperatureResult.tsx
│   │   │   └── index.ts
│   │   ├── scope/
│   │   │   ├── FinalizerCard.tsx
│   │   │   ├── ScopeStack.tsx
│   │   │   └── utils.ts
│   │   └── ui/
│   │       ├── QuickOpen.tsx
│   │       ├── SegmentedControl.tsx
│   │       ├── VolumeToggle.tsx
│   │       └── index.ts
│   ├── constants/
│   │   ├── colors.ts
│   │   └── dimensions.ts
│   ├── examples/
│   │   ├── effect-acquire-release.tsx
│   │   ├── effect-add-finalizer.tsx
│   │   ├── effect-all-short-circuit.tsx
│   │   ├── effect-all.tsx
│   │   ├── effect-die.tsx
│   │   ├── effect-eventually.tsx
│   │   ├── effect-fail.tsx
│   │   ├── effect-firstsuccessof.tsx
│   │   ├── effect-foreach.tsx
│   │   ├── effect-orelse.tsx
│   │   ├── effect-partition.tsx
│   │   ├── effect-promise.tsx
│   │   ├── effect-race.tsx
│   │   ├── effect-raceall.tsx
│   │   ├── effect-repeat-spaced.tsx
│   │   ├── effect-repeat-while-output.tsx
│   │   ├── effect-retry-exponential.tsx
│   │   ├── effect-retry-recurs.tsx
│   │   ├── effect-sleep.tsx
│   │   ├── effect-succeed.tsx
│   │   ├── effect-sync.tsx
│   │   ├── effect-timeout.tsx
│   │   ├── effect-validate.tsx
│   │   ├── helpers.ts
│   │   ├── ref-make.tsx
│   │   └── ref-update-and-get.tsx
│   ├── hooks/
│   │   ├── useOptionKey.ts
│   │   ├── useStateTransition.ts
│   │   ├── useVisualEffects.ts
│   │   └── useVisualScope.ts
│   ├── lib/
│   │   ├── example-types.ts
│   │   └── examples-manifest.ts
│   ├── shared/
│   │   ├── appItems.ts
│   │   └── idUtils.ts
│   ├── sounds/
│   │   └── TaskSounds.ts
│   └── theme.ts
├── tailwind.config.js
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.scripts.json
├── vitest.config.ts
└── wrangler.jsonc
Condensed preview — 109 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (338K chars).
[
  {
    "path": ".gitignore",
    "chars": 353,
    "preview": "# Logs\nlogs\n*.log\n.next\nout/\n.vscode\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nno..."
  },
  {
    "path": "CLAUDE.md",
    "chars": 12647,
    "preview": "# Visual Effect - Codebase Documentation\n\n## Overview\n\nVisual Effect is an interactive visualization tool for the Effect..."
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2025 Kit Langton\n\nPermission is hereby granted, free of charge, to any person obtaining a cop..."
  },
  {
    "path": "README.md",
    "chars": 426,
    "preview": "# Visual Effect\n\nInteractive visualizations for [Effect](https://github.com/Effect-TS/effect) programs, built with Effec..."
  },
  {
    "path": "app/ClientAppContent.tsx",
    "chars": 257,
    "preview": "\"use client\"\n\nimport dynamic from \"next/dynamic\"\n\nconst AppContent = dynamic(\n  () => import(\"../src/AppContent\").then(m..."
  },
  {
    "path": "app/[exampleId]/page.tsx",
    "chars": 1810,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport type { Metadata } from \"next\"\nimport { examplesManifest..."
  },
  {
    "path": "app/globals.css",
    "chars": 841,
    "preview": "@import \"tailwindcss\";\n\n/* Dark mode scrollbar for all elements */\n* {\n  scrollbar-width: thin;\n  scrollbar-color: #2626..."
  },
  {
    "path": "app/layout.tsx",
    "chars": 1838,
    "preview": "/* eslint-disable react-refresh/only-export-components */\nimport \"./globals.css\"\nimport ClientAppContent from \"./ClientA..."
  },
  {
    "path": "app/page.tsx",
    "chars": 53,
    "preview": "export default function HomePage() {\n  return null\n}\n"
  },
  {
    "path": "biome.json",
    "chars": 1452,
    "preview": "{\n  \"$schema\": \"https://biomejs.dev/schemas/2.2.5/schema.json\",\n  \"assist\": { \"actions\": { \"source\": { \"organizeImports\"..."
  },
  {
    "path": "next-env.d.ts",
    "chars": 260,
    "preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n/// <reference path=\"./out/types/routes..."
  },
  {
    "path": "next.config.js",
    "chars": 678,
    "preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  turbopack: {},\n  output: \"export\",\n  trailingSlash: true..."
  },
  {
    "path": "package.json",
    "chars": 2471,
    "preview": "{\n  \"name\": \"visual-effect\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"sideEffects\": false,\n  \"lic..."
  },
  {
    "path": "postcss.config.mjs",
    "chars": 69,
    "preview": "export default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n}\n"
  },
  {
    "path": "public/_headers",
    "chars": 302,
    "preview": "/*\n  X-Content-Type-Options: nosniff\n  X-Frame-Options: DENY\n  X-XSS-Protection: 1; mode=block\n\n/_next/static/*\n  Cache-..."
  },
  {
    "path": "public/generate-favicons.html",
    "chars": 1168,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Generate Favicons</title>\n</head>\n<body>\n    <h1>Generate PNG Favicons from SVG..."
  },
  {
    "path": "public/robots.txt",
    "chars": 125,
    "preview": "# robots.txt for Visual Effect\n\nUser-agent: *\nAllow: /\n\n# Sitemap location\nSitemap: https://effect.kitlangton.com/sitema..."
  },
  {
    "path": "public/site.webmanifest",
    "chars": 468,
    "preview": "{\n  \"name\": \"Visual Effect - Interactive Effect Library Visualizer\",\n  \"short_name\": \"Visual Effect\",\n  \"description\": \"..."
  },
  {
    "path": "public/sitemap.xml",
    "chars": 271,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <url>\n    <loc>htt..."
  },
  {
    "path": "scripts/generate-og-images.tsx",
    "chars": 6457,
    "preview": "/* eslint-disable react-refresh/only-export-components */\n\nimport fs from \"node:fs/promises\"\nimport path from \"node:path..."
  },
  {
    "path": "src/AppContent.tsx",
    "chars": 11495,
    "preview": "\"use client\"\n\nimport {\n  ArrowClockwiseIcon,\n  HashStraightIcon,\n  HeartIcon,\n  PlayIcon,\n  SkullIcon,\n  StopIcon,\n} fro..."
  },
  {
    "path": "src/VisualEffect.test.ts",
    "chars": 9250,
    "preview": "import { Effect } from \"effect\"\nimport { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\"\nimport { Visual..."
  },
  {
    "path": "src/VisualEffect.ts",
    "chars": 11513,
    "preview": "\"use client\"\n\nimport { Context, Effect, Fiber, Option } from \"effect\"\nimport { useSyncExternalStore } from \"react\"\nimpor..."
  },
  {
    "path": "src/VisualRef.ts",
    "chars": 3177,
    "preview": "\"use client\"\n\nimport { Effect, Ref } from \"effect\"\nimport { useMemo, useSyncExternalStore } from \"react\"\nimport { taskSo..."
  },
  {
    "path": "src/VisualScope.ts",
    "chars": 2177,
    "preview": "import { taskSounds } from \"./sounds/TaskSounds\"\n\nexport type ScopeState = \"idle\" | \"acquiring\" | \"active\" | \"releasing\"..."
  },
  {
    "path": "src/animations.ts",
    "chars": 3310,
    "preview": "// Animation configuration for consistent motion design across components\n\n// Default spring for MotionConfig wrapper\nex..."
  },
  {
    "path": "src/components/CodeBlock.tsx",
    "chars": 6384,
    "preview": "import type { MotionStyle } from \"motion/react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport type { Lan..."
  },
  {
    "path": "src/components/HeaderView.tsx",
    "chars": 10437,
    "preview": "\"use client\"\nimport {\n  ArrowCounterClockwiseIcon,\n  CheckIcon,\n  LinkIcon,\n  PlayIcon,\n  StarFourIcon,\n  StopIcon,\n} fr..."
  },
  {
    "path": "src/components/ScheduleTimeline.tsx",
    "chars": 20576,
    "preview": "import { motion } from \"motion/react\"\nimport React, { useEffect, useRef, useState } from \"react\"\nimport type { VisualEff..."
  },
  {
    "path": "src/components/Timer.tsx",
    "chars": 1690,
    "preview": "import { useEffect, useRef, useState } from \"react\"\nimport type { VisualEffect } from \"@/VisualEffect\"\n\nfunction useTime..."
  },
  {
    "path": "src/components/display/EffectExample.tsx",
    "chars": 10557,
    "preview": "\"use client\"\n\nimport { ArrowRightIcon } from \"@phosphor-icons/react\"\nimport { motion } from \"motion/react\"\nimport { memo..."
  },
  {
    "path": "src/components/display/RefDisplay.tsx",
    "chars": 2528,
    "preview": "import { AnimatePresence, motion, type Transition } from \"motion/react\"\nimport { useVisualRef, type VisualRef } from \"@/..."
  },
  {
    "path": "src/components/display/index.ts",
    "chars": 145,
    "preview": "export { ScheduleTimeline } from \"../ScheduleTimeline\"\nexport { EffectExample } from \"./EffectExample\"\nexport { RefDispl..."
  },
  {
    "path": "src/components/effect/EffectContainer.tsx",
    "chars": 3004,
    "preview": "import { motion, useTransform } from \"motion/react\"\nimport { colors, effects } from \"@/animations\"\nimport type { VisualE..."
  },
  {
    "path": "src/components/effect/EffectContent.tsx",
    "chars": 3906,
    "preview": "import { SkullIcon, StarFourIcon, WarningOctagonIcon } from \"@phosphor-icons/react\"\nimport { AnimatePresence, motion } f..."
  },
  {
    "path": "src/components/effect/EffectLabel.tsx",
    "chars": 825,
    "preview": "import { motion } from \"motion/react\"\nimport { Timer } from \"@/components/Timer\"\nimport { theme } from \"@/theme\"\nimport..."
  },
  {
    "path": "src/components/effect/EffectNode.tsx",
    "chars": 3254,
    "preview": "import { AnimatePresence, motion } from \"motion/react\"\nimport { memo, useCallback, useState } from \"react\"\nimport {\n  us..."
  },
  {
    "path": "src/components/effect/EffectOverlay.tsx",
    "chars": 2089,
    "preview": "import { motion } from \"motion/react\"\nimport { theme } from \"../../theme\"\nimport type { EffectMotionValues } from \"./use..."
  },
  {
    "path": "src/components/effect/index.ts",
    "chars": 491,
    "preview": "export { EffectContainer } from \"./EffectContainer\"\nexport { EffectContent } from \"./EffectContent\"\nexport { EffectLabel..."
  },
  {
    "path": "src/components/effect/nodeVariants.ts",
    "chars": 1974,
    "preview": "import { springs } from \"@/animations\"\nimport { TASK_COLORS } from \"../../constants/colors\"\n\n// Hybrid approach: Only ha..."
  },
  {
    "path": "src/components/effect/taskUtils.ts",
    "chars": 390,
    "preview": "import { SHADOW_COLORS } from \"../../constants/colors\"\nimport { theme } from \"../../theme\"\nimport type { VisualEffect }..."
  },
  {
    "path": "src/components/effect/useEffectMotion.ts",
    "chars": 13034,
    "preview": "import {\n  type AnimationPlaybackControls,\n  animate,\n  type MotionValue,\n  useMotionValue,\n  useSpring,\n  useTransform,..."
  },
  {
    "path": "src/components/feedback/DeathBubble.tsx",
    "chars": 4191,
    "preview": "\"use client\"\n\nimport { animate, motion, useMotionValue, useTransform } from \"motion/react\"\nimport { useEffect, useMemo }..."
  },
  {
    "path": "src/components/feedback/EffectLogo.tsx",
    "chars": 4313,
    "preview": "import { motion, type Transition } from \"motion/react\"\n\nexport function EffectLogo({ className = \"h-7\" }: { className?:..."
  },
  {
    "path": "src/components/feedback/FailureBubble.tsx",
    "chars": 4048,
    "preview": "\"use client\"\n\nimport { animate, motion, useMotionValue, useTransform } from \"motion/react\"\nimport { useEffect } from \"re..."
  },
  {
    "path": "src/components/feedback/FloatingHighlight.tsx",
    "chars": 5898,
    "preview": "import { animate, motion, useMotionValue, useSpring, useTransform } from \"motion/react\"\nimport { useEffect, useRef } fro..."
  },
  {
    "path": "src/components/feedback/NotificationBubble.tsx",
    "chars": 2819,
    "preview": "\"use client\"\n\nimport { animate, motion, useMotionValue, useTransform } from \"motion/react\"\nimport { useEffect } from \"re..."
  },
  {
    "path": "src/components/feedback/index.ts",
    "chars": 248,
    "preview": "export { DeathBubble } from \"./DeathBubble\"\nexport { EffectLogo } from \"./EffectLogo\"\nexport { FailureBubble } from \"./F..."
  },
  {
    "path": "src/components/index.ts",
    "chars": 305,
    "preview": "// Content components\nexport * from \"./CodeBlock\"\n// Display components\nexport * from \"./display\"\n// Effect visualizatio..."
  },
  {
    "path": "src/components/layout/NavigationSidebar.tsx",
    "chars": 3234,
    "preview": "import { HashStraightIcon } from \"@phosphor-icons/react\"\nimport { memo, useMemo } from \"react\"\n\ninterface Example {\n  id..."
  },
  {
    "path": "src/components/layout/PageHeader.tsx",
    "chars": 3112,
    "preview": "import { StarFourIcon } from \"@phosphor-icons/react\"\nimport { motion, useAnimationFrame, useMotionValue, useTransform, u..."
  },
  {
    "path": "src/components/renderers/ArrayResult.tsx",
    "chars": 479,
    "preview": "\"use client\"\n\nimport { motion } from \"motion/react\"\nimport type { RenderableResult } from \"./RenderableResult\"\n\nexport c..."
  },
  {
    "path": "src/components/renderers/BasicRenderers.tsx",
    "chars": 962,
    "preview": "import type { RenderableResult } from \"./RenderableResult\"\n\n// Simple number renderer\nexport class NumberResult implemen..."
  },
  {
    "path": "src/components/renderers/EmojiResult.tsx",
    "chars": 274,
    "preview": "import type { RenderableResult } from \"./RenderableResult\"\n\nexport class EmojiResult implements RenderableResult {\n  con..."
  },
  {
    "path": "src/components/renderers/RenderableResult.ts",
    "chars": 753,
    "preview": "import type { ReactNode } from \"react\"\n\n// Base interface for renderable results\nexport interface RenderableResult {\n  r..."
  },
  {
    "path": "src/components/renderers/TemperatureResult.tsx",
    "chars": 579,
    "preview": "import type { RenderableResult } from \"./RenderableResult\"\n\nexport class TemperatureResult implements RenderableResult {..."
  },
  {
    "path": "src/components/renderers/index.ts",
    "chars": 164,
    "preview": "export * from \"./ArrayResult\"\nexport * from \"./BasicRenderers\"\nexport * from \"./EmojiResult\"\nexport * from \"./Renderable..."
  },
  {
    "path": "src/components/scope/FinalizerCard.tsx",
    "chars": 5504,
    "preview": "import { AnimatePresence, motion } from \"motion/react\"\nimport { useEffect, useRef, useState } from \"react\"\nimport { spri..."
  },
  {
    "path": "src/components/scope/ScopeStack.tsx",
    "chars": 6549,
    "preview": "import { CaretRightIcon } from \"@phosphor-icons/react\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport { u..."
  },
  {
    "path": "src/components/scope/utils.ts",
    "chars": 671,
    "preview": "import type { VisualScope } from \"../../VisualScope\"\n\nexport function isActive(state: VisualScope[\"state\"]) {\n  return s..."
  },
  {
    "path": "src/components/ui/QuickOpen.tsx",
    "chars": 9387,
    "preview": "import type React from \"react\"\nimport { useCallback, useEffect, useRef, useState } from \"react\"\n\ninterface QuickOpenItem..."
  },
  {
    "path": "src/components/ui/SegmentedControl.tsx",
    "chars": 2747,
    "preview": "import { motion } from \"motion/react\"\nimport { useEffect, useRef, useState } from \"react\"\n\ninterface SegmentedControlPro..."
  },
  {
    "path": "src/components/ui/VolumeToggle.tsx",
    "chars": 3786,
    "preview": "import { AnimatePresence, motion, useSpring } from \"motion/react\"\nimport { useEffect, useRef } from \"react\"\nimport { tas..."
  },
  {
    "path": "src/components/ui/index.ts",
    "chars": 140,
    "preview": "export { QuickOpen } from \"./QuickOpen\"\nexport { SegmentedControl } from \"./SegmentedControl\"\nexport { VolumeToggle } fr..."
  },
  {
    "path": "src/constants/colors.ts",
    "chars": 513,
    "preview": "// Centralized color constants for task states\nexport const TASK_COLORS = {\n  idle: \"var(--color-slate-600)\",\n  running:..."
  },
  {
    "path": "src/constants/dimensions.ts",
    "chars": 359,
    "preview": "// Layout and dimensional constants\nexport const dimensions = {\n  // Effect node dimensions\n  node: {\n    width: 64,..."
  },
  {
    "path": "src/examples/effect-acquire-release.tsx",
    "chars": 5321,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useEffect, useMemo, useRef } from \"react\"\nimport { EffectExample..."
  },
  {
    "path": "src/examples/effect-add-finalizer.tsx",
    "chars": 5290,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { memo, useEffect, useMemo, useState } from \"react\"\nimport { Effect..."
  },
  {
    "path": "src/examples/effect-all-short-circuit.tsx",
    "chars": 2887,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo, useRef } from \"react\"\nimport { EffectExample } from \"@/c..."
  },
  {
    "path": "src/examples/effect-all.tsx",
    "chars": 5231,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { memo, useCallback, useMemo, useState } from \"react\"\nimport { Effe..."
  },
  {
    "path": "src/examples/effect-die.tsx",
    "chars": 1365,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-eventually.tsx",
    "chars": 2464,
    "preview": "\"use client\"\n\nimport { Effect, Schedule } from \"effect\"\nimport { useMemo, useRef } from \"react\"\nimport { EffectExample }..."
  },
  {
    "path": "src/examples/effect-fail.tsx",
    "chars": 1034,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-firstsuccessof.tsx",
    "chars": 2668,
    "preview": "\"use client\";\n\nimport { Effect } from \"effect\";\nimport { useMemo } from \"react\";\nimport { EffectExample } from \"@/compon..."
  },
  {
    "path": "src/examples/effect-foreach.tsx",
    "chars": 2105,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-orelse.tsx",
    "chars": 2579,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo, useRef } from \"react\"\nimport { EffectExample } from \"@/c..."
  },
  {
    "path": "src/examples/effect-partition.tsx",
    "chars": 2654,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-promise.tsx",
    "chars": 1264,
    "preview": "\"use client\"\n\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/components/display\"\nimport { useVisualEff..."
  },
  {
    "path": "src/examples/effect-race.tsx",
    "chars": 1914,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-raceall.tsx",
    "chars": 2151,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-repeat-spaced.tsx",
    "chars": 2738,
    "preview": "\"use client\"\n\nimport { Effect, Schedule } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@..."
  },
  {
    "path": "src/examples/effect-repeat-while-output.tsx",
    "chars": 2642,
    "preview": "\"use client\"\n\nimport { Duration, Effect, Schedule } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample..."
  },
  {
    "path": "src/examples/effect-retry-exponential.tsx",
    "chars": 2235,
    "preview": "\"use client\"\n\nimport { Effect, Schedule } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@..."
  },
  {
    "path": "src/examples/effect-retry-recurs.tsx",
    "chars": 2934,
    "preview": "\"use client\"\n\nimport { Effect, Schedule } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@..."
  },
  {
    "path": "src/examples/effect-sleep.tsx",
    "chars": 1456,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-succeed.tsx",
    "chars": 1139,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-sync.tsx",
    "chars": 1169,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } from \"@/component..."
  },
  {
    "path": "src/examples/effect-timeout.tsx",
    "chars": 2021,
    "preview": "\"use client\"\n\nimport { Effect } from \"effect\"\nimport { useMemo, useRef } from \"react\"\nimport { EffectExample } from \"@/c..."
  },
  {
    "path": "src/examples/effect-validate.tsx",
    "chars": 6481,
    "preview": "\"use client\"\n\nimport { Cause, Effect } from \"effect\"\nimport { AnimatePresence, motion } from \"motion/react\"\nimport { use..."
  },
  {
    "path": "src/examples/helpers.ts",
    "chars": 2074,
    "preview": "import { Effect } from \"effect\"\nimport { EmojiResult, TemperatureResult } from \"../components/renderers\"\n\n/**\n * Generat..."
  },
  {
    "path": "src/examples/ref-make.tsx",
    "chars": 2707,
    "preview": "\"use client\"\n\nimport { Duration, Effect, Ref, Schedule } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectEx..."
  },
  {
    "path": "src/examples/ref-update-and-get.tsx",
    "chars": 4073,
    "preview": "\"use client\"\n\nimport { Duration, Effect, Ref } from \"effect\"\nimport { useMemo } from \"react\"\nimport { EffectExample } fr..."
  },
  {
    "path": "src/hooks/useOptionKey.ts",
    "chars": 1862,
    "preview": "import { useEffect, useState } from \"react\"\n\nlet globalOptionPressed = false\nconst subscribers: Set<(pressed: boolean) =..."
  },
  {
    "path": "src/hooks/useStateTransition.ts",
    "chars": 1016,
    "preview": "import { useEffect, useRef } from \"react\"\nimport type { EffectState } from \"../VisualEffect\"\n\nexport interface StateTran..."
  },
  {
    "path": "src/hooks/useVisualEffects.ts",
    "chars": 1921,
    "preview": "import type { Effect } from \"effect\"\nimport { type DependencyList, useMemo } from \"react\"\nimport { type VisualEffect, vi..."
  },
  {
    "path": "src/hooks/useVisualScope.ts",
    "chars": 295,
    "preview": "import { useEffect, useReducer } from \"react\"\nimport type { VisualScope } from \"../VisualScope\"\n\nexport function useVisu..."
  },
  {
    "path": "src/lib/example-types.ts",
    "chars": 427,
    "preview": "export interface ExampleMeta {\n  id: string\n  name: string\n  variant?: string\n  description: string\n  section: \"construc..."
  },
  {
    "path": "src/lib/examples-manifest.ts",
    "chars": 5160,
    "preview": "import type { ExampleMeta } from \"./example-types\"\n\n// This is the single source of truth for all examples\n// Examples a..."
  },
  {
    "path": "src/shared/appItems.ts",
    "chars": 298,
    "preview": "import type { AppItem } from \"../lib/example-types\"\nimport { examplesManifest } from \"../lib/examples-manifest\"\nimport {..."
  },
  {
    "path": "src/shared/idUtils.ts",
    "chars": 373,
    "preview": "function normalizeSegment(value: string): string {\n  return value\n    .replace(/([a-z0-9])([A-Z])/g, \"$1-$2\")\n    .toLow..."
  },
  {
    "path": "src/sounds/TaskSounds.ts",
    "chars": 14099,
    "preview": "import * as Tone from \"tone\"\n\n// Musical scale configuration\nconst PENTATONIC_SCALE = [\"C\", \"D\", \"E\", \"G\", \"A\"] as const..."
  },
  {
    "path": "src/theme.ts",
    "chars": 305,
    "preview": "// Design tokens - only the values actually used in the codebase\nexport const theme = {\n  colors: {\n    textPrimary: \"#f..."
  },
  {
    "path": "tailwind.config.js",
    "chars": 298,
    "preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [\"./app/**/*.{js,ts,jsx,tsx,mdx}\", \"./src/**/*.{..."
  },
  {
    "path": "tsconfig.app.json",
    "chars": 1014,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2022\", \"DOM\", \"DOM...."
  },
  {
    "path": "tsconfig.json",
    "chars": 1061,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": false,..."
  },
  {
    "path": "tsconfig.node.json",
    "chars": 666,
    "preview": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",..."
  },
  {
    "path": "tsconfig.scripts.json",
    "chars": 418,
    "preview": "{\n  \"extends\": \"./tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"module\": \"ESNext\",\n    \"target\": \"ES2022\",\n    \"jsx\":..."
  },
  {
    "path": "vitest.config.ts",
    "chars": 139,
    "preview": "import { defineConfig } from \"vitest/config\"\n\nexport default defineConfig({\n  test: {\n    environment: \"jsdom\",\n    glob..."
  },
  {
    "path": "wrangler.jsonc",
    "chars": 401,
    "preview": "{\n  \"$schema\": \"./node_modules/wrangler/config-schema.json\",\n  \"name\": \"visual-effect\",\n  \"compatibility_date\": \"2026-07..."
  }
]

About this extraction

This page contains the full source code of the kitlangton/visual-effect GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 109 files (309.2 KB), approximately 79.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!