# DatamoshTransition

> Page-transition overlay that looks like a corrupted video decode: a fixed grid of columns, narrow on the left and wide on the right, fills with flat blocks of saturated colour that fall, snap open through the middle of the frame and squeeze shut again; cover() drops the columns over the page right to left and reveal() lets them fall away, both as promises for any router's navigation hook.

- Package: `fancy-ui-svelte` (Svelte 5 + Tailwind CSS v4)
- Category: Effects
- Tags: page transition, route, navigation, overlay, canvas, datamosh, glitch, columns
- Docs: https://fancy-ui.rama.app/docs/components/datamosh-transition
- Source: https://github.com/RamaHerbin/fancy-ui/tree/main/src/lib/fancy-ui/datamosh-transition

## Import

```svelte
<script lang="ts">
  import { DatamoshTransition } from 'fancy-ui-svelte';
</script>
```

Setup (once per project): `npm install fancy-ui-svelte`, then add `@import "fancy-ui-svelte/tailwind.css";` after `@import "tailwindcss";` in your main CSS file. Full guide: https://fancy-ui.rama.app/llms.txt

React: `import { DatamoshTransition } from 'fancy-ui-react';` (same prop names; `class` becomes `className`, snippets become `ReactNode`).

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors` | `"broadcast" \| "sunset" \| "thermal" \| "mono" \| "acid" \| string[]` | `"broadcast"` | Palette preset or colour list, hex or rgb(); the lightest and darkest colours recur most often |
| `variant` | `"curtain" \| "rise" \| "split" \| "interlace"` | `"curtain"` | Shape of the cover: drop from the top, climb from the bottom, open from the centre line, or alternate curtain and rise per column |
| `sweep` | `"right" \| "left" \| "center" \| "edges" \| "random"` | `"right"` | Order the columns move in: from the right, from the left, out from the middle, in from both sides, or a seeded shuffle |
| `source` | `string \| HTMLImageElement \| HTMLCanvasElement` | — | Picture to decode: tiles take their colours from it, auto-levelled and saturated, instead of the palette; URLs must be same-origin or CORS-enabled |
| `seed` | `number` | `1` | Seed for the colour order; same seed, same strip |
| `columns` | `number` | `11` | Number of columns |
| `power` | `number` | `1.65` | Column edge exponent: 1 is a uniform grid, higher widens the columns to the right |
| `tiles` | `number` | `15` | Tiles per column stack, forced odd so one tile takes the centre |
| `coverDuration` | `number` | `380` | Time for one column to cover, in ms |
| `revealDuration` | `number` | `480` | Time for one column to clear, in ms |
| `stagger` | `number` | `28` | Delay between neighbouring columns in ms, rightmost first |
| `speed` | `number` | `1` | Speed of the falling tiles |
| `contained` | `boolean` | `false` | Fill the positioned parent instead of the viewport |
| `zIndex` | `number` | `9999` | Stacking order of the overlay |
| `phase` | `"idle" \| "covering" \| "covered" \| "revealing"` | `"idle"` | Current phase; bindable, read-only |
| `oncovered` | `() => void` | — | Called once the page is fully covered |
| `onrevealed` | `() => void` | — | Called once the page is fully revealed |
| `ref` | `HTMLDivElement \| null` | `null` | Bindable overlay element |
| `class` | `string` | — | Additional classes on the overlay |

## Examples

### Basic Usage

```svelte
<script lang="ts">
	import { DatamoshTransition } from "fancy-ui-svelte";

	const pages = [
		{ title: "Page one", body: "Press the button to go to the next page." },
		{ title: "Page two", body: "The page changed while the screen was covered." },
	];

	let transition: ReturnType<typeof DatamoshTransition>;
	let current = $state(0);
	let busy = $state(false);

	async function navigate() {
		if (busy) return;
		busy = true;
		await transition.cover();
		current = (current + 1) % pages.length;
		await transition.reveal();
		busy = false;
	}
</script>

<div
	class="relative flex h-80 w-full flex-col items-center justify-center gap-4 overflow-hidden rounded-2xl border border-white/10 bg-[#14101f] text-center text-white"
>
	<h3 class="text-3xl font-semibold tracking-tight">{pages[current].title}</h3>
	<p class="text-sm text-white/60">{pages[current].body}</p>
	<button
		class="rounded-full bg-white px-4 py-2 text-sm font-medium text-[#14101f] disabled:opacity-60"
		onclick={navigate}
		disabled={busy}
	>
		Next page
	</button>
	<DatamoshTransition bind:this={transition} contained />
</div>
```

### Variants

Every cover shape, column sweep and palette preset.

```svelte
<script lang="ts">
	import {
		DatamoshTransition,
		type DatamoshEffect,
		type DatamoshPaletteName,
		type DatamoshSweep,
	} from "fancy-ui-svelte";

	const variants: DatamoshEffect[] = ["curtain", "rise", "split", "interlace"];
	const sweeps: DatamoshSweep[] = ["right", "left", "center", "edges", "random"];
	const palettes: DatamoshPaletteName[] = ["broadcast", "thermal", "mono", "acid"];

	let variant = $state<DatamoshEffect>("split");
	let sweep = $state<DatamoshSweep>("center");
	let colors = $state<DatamoshPaletteName>("thermal");
	let transition: ReturnType<typeof DatamoshTransition>;
	let page = $state(1);
	let busy = $state(false);

	async function navigate() {
		if (busy) return;
		busy = true;
		await transition.cover();
		page += 1;
		await transition.reveal();
		busy = false;
	}
</script>

<div class="flex w-full flex-col gap-4">
	<div class="flex flex-wrap gap-x-6 gap-y-3 text-sm">
		{#each [["variant", variants], ["sweep", sweeps], ["colors", palettes]] as const as [label, options] (label)}
			<fieldset class="flex flex-wrap items-center gap-1.5">
				<legend class="sr-only">{label}</legend>
				<span class="text-muted-foreground mr-1">{label}</span>
				{#each options as option (option)}
					{@const active =
						(label === "variant" && variant === option) ||
						(label === "sweep" && sweep === option) ||
						(label === "colors" && colors === option)}
					<button
						class="rounded-full border px-2.5 py-1 text-xs transition-colors {active
							? 'border-foreground bg-foreground text-background'
							: 'border-border hover:bg-muted'}"
						aria-pressed={active}
						onclick={() => {
							if (label === "variant") variant = option as DatamoshEffect;
							else if (label === "sweep") sweep = option as DatamoshSweep;
							else colors = option as DatamoshPaletteName;
						}}
					>
						{option}
					</button>
				{/each}
			</fieldset>
		{/each}
	</div>

	<div
		class="relative flex h-80 w-full flex-col items-center justify-center gap-4 overflow-hidden rounded-2xl border border-white/10 bg-[#14101f] text-center text-white"
	>
		<p class="font-mono text-xs tracking-widest text-white/50">
			PAGE {String(page).padStart(2, "0")}
		</p>
		<h3 class="text-3xl font-semibold tracking-tight">{variant} · {sweep}</h3>
		<button
			class="rounded-full bg-white px-4 py-2 text-sm font-medium text-[#14101f] disabled:opacity-60"
			onclick={navigate}
			disabled={busy}
		>
			Next page
		</button>
		<DatamoshTransition bind:this={transition} {variant} {sweep} {colors} contained />
	</div>
</div>
```

### Sunset

A vector trace of a sunset photo that sets into dusk. With the picture source on, the tiles decode the scene being left.

```svelte
<script lang="ts">
	import { DatamoshTransition } from "fancy-ui-svelte";

	type Scene = "golden" | "dusk";

	/** A vector trace of a sunset photo; dusk is the same picture, filtered. */
	const PICTURE = "/examples/datamosh-sunset.svg";
	const DUSK_FILTER = "brightness(0.45) saturate(1.1) hue-rotate(-100deg)";

	let scene = $state<Scene>("golden");
	let picture = $state(true);
	let busy = $state(false);
	let source = $state<string | HTMLCanvasElement | undefined>();
	let transition: ReturnType<typeof DatamoshTransition>;
	let imgEl: HTMLImageElement;

	/** The scene being left, as something the transition can decode. */
	async function snapshot(): Promise<string | HTMLCanvasElement> {
		if (scene === "golden") return PICTURE;
		// Dusk only exists as a CSS filter: bake it into a small canvas.
		await imgEl.decode().catch(() => {});
		const canvas = document.createElement("canvas");
		canvas.width = 384;
		canvas.height = 512;
		const ctx = canvas.getContext("2d");
		if (!ctx) return PICTURE;
		ctx.filter = DUSK_FILTER;
		ctx.drawImage(imgEl, 0, 0, canvas.width, canvas.height);
		return canvas;
	}

	async function next() {
		if (busy) return;
		busy = true;
		source = picture ? await snapshot() : undefined;
		await transition.cover();
		scene = scene === "golden" ? "dusk" : "golden";
		await new Promise((r) => setTimeout(r, 300));
		await transition.reveal();
		busy = false;
	}
</script>

<div class="flex w-full flex-col items-center gap-3">
	<div class="flex flex-wrap items-center justify-center gap-3 text-sm">
		<button
			class="bg-foreground text-background rounded-full px-4 py-1.5 font-medium disabled:opacity-60"
			onclick={next}
			disabled={busy}
		>
			{scene === "golden" ? "Let the sun set" : "Back to golden hour"}
		</button>
		<label class="text-muted-foreground flex items-center gap-2">
			<input type="checkbox" bind:checked={picture} />
			Tiles decode the picture
		</label>
	</div>

	<div
		class="border-border relative aspect-[3/4] w-full max-w-sm overflow-hidden rounded-2xl border bg-[#2d1c16]"
	>
		<img
			bind:this={imgEl}
			src={PICTURE}
			alt={scene === "golden"
				? "Vector sunset: an oval sun on the sea horizon behind a sailboat, under an orange sky streaked with clouds"
				: "The same seascape at dusk, in violet light"}
			class="block h-full w-full object-cover"
			style:filter={scene === "dusk" ? DUSK_FILTER : "none"}
			decoding="async"
		/>

		<DatamoshTransition
			bind:this={transition}
			variant="split"
			sweep="center"
			colors="sunset"
			{source}
			contained
		/>
	</div>
</div>
```
