# Perspective Squeeze
URL: https://remocn.dev/docs/typography/perspective-squeeze

Two lines of heavy type endlessly trade height while corner-pinned perspective bends the flat stack into receding planes

- Install: `npx shadcn@latest add @remocn/perspective-squeeze`
- Vibe: playful
- Natural length: 225f @ 30fps

## Installation

```bash
npx shadcn@latest add @remocn/perspective-squeeze
```

## Usage

```tsx
// src/Root.tsx
import { Composition } from "remotion";
import { PerspectiveSqueeze } from "@/components/remocn/perspective-squeeze";

const PerspectiveSqueezeScene = () => (
  <PerspectiveSqueeze
    upperText="REMOCN"
    lowerText="BEST"
    upperFontFamily="Impact, 'Arial Narrow', sans-serif"
    lowerFontFamily="Impact, 'Arial Narrow', sans-serif"
  />
);

export const RemotionRoot = () => (
  <Composition
    id="PerspectiveSqueeze"
    component={PerspectiveSqueezeScene}
    durationInFrames={225}
    fps={30}
    width={1920}
    height={1080}
  />
);
```

Both lines are forced to exactly the block's width via SVG `textLength`, so any
pair of words lines up flush. The vertical squeeze scales each line about its
own baseline, and the layout re-derives from the scaled heights every frame —
the gap between the lines never changes. The perspective is a true projective
corner-pin (`matrix3d`), interpolated on the corners, so straight lines stay
straight while parallels converge.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `upperText / lowerText` | `string` | `"REMOCN" / "BEST"` | The two lines. Both are stretched to the block's full width |
| `upperFontFamily / lowerFontFamily` | `string` | `"sans-serif"` | Font of each line — heavy condensed faces read best |
| `upperFontSize / lowerFontSize` | `number` | `603 / 124` | Font size of each line in block pixels |
| `fill` | `string` | `"#ffffff"` | Fill color of the type |
| `width / height` | `number` | `1920 / 1080` | Design space all positions are authored in. The stage scales to fit the actual composition, centered |
| `blockWidth / blockHeight` | `number` | `990 / 546` | Size of the cropped block the corner pin acts on — cropping before pinning keeps the corners near the artwork |
| `upperBlockPosition / lowerBlockPosition` | `[number, number]` | `[960, 314] / [960, 860]` | Center of each pinned copy in comp pixels |
| `cornerKeyframes` | `number[]` | `[0, 45, 70]` | Frames at which the corner poses land |
| `upperCornersMid / upperCornersEnd` | `Corners` | `tutorial poses` | Corner poses of the upper copy, as four `[x, y]` points in block space |
| `lowerCornersMid / lowerCornersEnd` | `Corners` | `tutorial poses` | Corner poses of the lower copy |
| `cornersRest` | `Corners` | `the block's own rectangle` | Identity pose every departure is measured from |
| `scaleKeyframes` | `number[]` | `[0, 60, 118, 161, 196, 225]` | Frames of the squish track. The last keyframe repeats the first, closing the loop |
| `upperScaleY / lowerScaleY` | `number[]` | `tutorial tracks` | Vertical scale percentages at each keyframe — the lines trade height in opposition |
| `capRatio` | `number` | `0.65` | Cap height as a fraction of em, used to stack the two lines |
| `lineGap` | `number` | `50` | Constant gap between the lines in block pixels |
| `className` | `string` | — | Optional className passed to the wrapper |

Inspired by [5 Clean Text Animations](https://www.youtube.com/watch?v=Nmi7cE2wT54) by Mapal.

## Use when
- A looping hold — an outro, a background title, a stream screen — needs constant, hypnotic typographic motion (the 225-frame track ends where it starts).
- You want an editorial poster feel: heavy condensed type crushed and stretched like a print layout come alive.
- Two short words can share the frame — the two lines are forced to the same width, so pairs like a brand name and a tagline work best.

## Don't use when
- You need an entrance or a reveal — this is a sustained loop, the type is fully visible from frame 0; use `kinetic-warp` for a punchy display moment.
- The copy must stay easily readable throughout — lines spend most of the loop crushed to a third of their height.
- You only need the vertical stretch without the perspective bend — drop-in alternatives like `per-character-rise` keep the type flat.