YJ.
Experimental Lab

Is Time Wide or Is it Long (Replica)

A modern 2026 JavaScript restoration of an interactive art piece by Hsin-Chien Huang, remade by Gemini 3.1.

Javascript Creative-Coding React Web-Audio AI-Restoration
Complexity5
Click to Fragment

A modern JavaScript recreation of an original Flash-based interactive art piece. This 2026 version was restored and remade by Gemini 3.1, based on the seminal work of Hsin-Chien Huang (Wikipedia).

Concept

The piece explores the recursive nature of time and space. A single square represents a moment or a whole, which can be split into smaller and smaller fragments. As it reaches its maximum complexity, it inevitably flows back into its original unified state.

Technical Implementation

Rebuilding this algorithmic experience required a combination of recursive data structures and precise physical simulation:

Recursive Tree Architecture

The logic uses a tree-based splitting algorithm. For each level of depth n, the algorithm traverses the current “moment” and divides it into two fragments. To keep the result organic, each split ratio is randomized between 20% and 80%, which ensures that every child still has enough geometric material to split again.

const splitPiece = (piece, step) => {
  const ratio = 0.2 + Math.random() * 0.6;
  const force = (15 + Math.random() * 55) * Math.pow(0.85, step);

  if (Math.random() > 0.5) {
    const h1 = piece.h * ratio;
    return [
      { id: `${piece.id}-1`, w: piece.w, h: h1, x: piece.x, y: piece.y - force },
      { id: `${piece.id}-2`, w: piece.w, h: piece.h - h1, x: piece.x, y: piece.y + h1 + force },
    ];
  }

  const w1 = piece.w * ratio;
  return [
    { id: `${piece.id}-1`, w: w1, h: piece.h, x: piece.x - force, y: piece.y },
    { id: `${piece.id}-2`, w: piece.w - w1, h: piece.h, x: piece.x + w1 + force, y: piece.y },
  ];
};

Coordinate Inheritance And Continuous Motion

A primary challenge was achieving smooth, non-jumping motion. Every fragment inherits its parent’s exact coordinates at the moment of fracture. The child starts perfectly overlapped with the parent, then slides toward a new target, which makes the decomposition feel continuous instead of snapping.

const child = {
  id: `${parent.id}-1`,
  x: parent.targetX,
  y: parent.targetY,
  w: childWidth,
  h: parent.h,
  startX: parent.targetX,
  startY: parent.targetY,
  targetX: parent.targetX - force,
  targetY: parent.targetY,
};

Ice Sliding Physics

To capture the original’s physical weight, I used a cubic-bezier impulse curve of [0.05, 0.9, 0.1, 1.0]. The movement starts with a sharp push and then eases into a long glide, which makes the fragments feel like they are slipping across a low-friction surface.

const animation = node.animate(
  [{ transform: from }, { transform: to }],
  {
    duration: 2200,
    easing: 'cubic-bezier(0.05, 0.9, 0.1, 1)',
    fill: 'forwards',
  }
);

Generative Web Audio Synthesis

The chimes are synthesized in real time with the Web Audio API. Each split triggers a small stack of oscillators built from a base frequency and a few non-integer harmonics, which produces the spacious, glassy tone. The merge phase mirrors that contour in reverse.

const playTone = (freq, volume, duration) => {
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();

  gain.gain.setValueAtTime(0, ctx.currentTime);
  gain.gain.linearRampToValueAtTime(volume, ctx.currentTime + 0.01);
  gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);

  osc.frequency.setValueAtTime(freq, ctx.currentTime);
  osc.connect(gain);
  gain.connect(ctx.destination);
  osc.start();
  osc.stop(ctx.currentTime + duration);
};

Atomic State Handover

To eliminate visual jitter, the merge sequence uses an atomic handover step. Children first animate back toward the parent’s origin, and only after they have visually reassembled does the parent rectangle replace them in state.

setPieces((current) => {
  const filtered = current.filter((piece) => !currentLevelIds.includes(piece.id));
  const restoredParents = parentLevel.map((piece) => ({
    ...piece,
    startX: piece.targetX,
    startY: piece.targetY,
  }));

  return [...filtered, ...restoredParents];
});

Browser Responsiveness Under Load

A later restoration pass exposed a performance bug in the modern version. During deeper fragmentation runs, every piece was being driven through a React-heavy SVG animation path, which could saturate the main thread badly enough that switching tabs felt stuck. Hidden-page transitions could also leave async waits and audio activity alive longer than intended.

Before, every fragment was animated through a per-piece Framer Motion node:

pieces.map((piece) => (
  <motion.rect
    key={piece.id}
    initial={{ x: piece.startX, y: piece.startY }}
    animate={{ x: piece.targetX, y: piece.targetY }}
    transition={{
      type: 'tween',
      ease: [0.05, 0.9, 0.1, 1],
      duration: 2.2,
    }}
  />
));

After, the interaction gained explicit teardown and the fragment motion was moved to lighter browser-native transforms:

const cancelInteraction = () => {
  interactionRunIdRef.current += 1;
  clearPendingWaits();
  stopActiveNotes();
  stopSilentAudio();
};

const AnimatedPiece = memo(({ piece, duration }) => {
  const ref = useRef(null);

  useLayoutEffect(() => {
    const node = ref.current;
    if (!node) return;

    const from = `translate(${piece.startX - piece.x}px, ${piece.startY - piece.y}px)`;
    const to = `translate(${piece.targetX - piece.x}px, ${piece.targetY - piece.y}px)`;
    const animation = node.animate(
      [{ transform: from }, { transform: to }],
      {
        duration: duration * 1000,
        easing: 'cubic-bezier(0.05, 0.9, 0.1, 1)',
        fill: 'forwards',
      }
    );

    return () => animation.cancel();
  }, [piece, duration]);

  return (
    <g ref={ref}>
      <rect x={piece.x} y={piece.y} width={piece.w} height={piece.h} />
    </g>
  );
});

ORIGINAL REFERENCE

This project was originally a replica of Hsin-Chien Huang’s work, created as part of a job application in 2009 using ActionScript 3.0. Rebuilding it today preserves the algorithmic beauty of the original interactive experience.

Screenshot of the original Flash-based version (2009).