# Iridescence

> Folded silk behind your content, its hue turning with each fold, stirred by the pointer.

Source: https://ngwr.dev/bits/iridescence  
Kind: Animation  
Added in v15.2

## Installation

Import the class, and `@use` the entry point in your global styles. Nothing warns you when the `@use` is missing.

```typescript
import { Component } from '@angular/core';
import { WrIridescence } from 'ngwr/bits/iridescence';

@Component({ imports: [WrIridescence] })
export class MyComponent {}
```

```scss
@use 'ngwr/bits/iridescence';
```

## Playground

Put it first in a positioned box; it fills that box. Give the content after it a position of its own, or the silk paints over it. Move the pointer across it to stir the folds.

```angular-html
<section style="position: relative; min-height: 18rem; overflow: hidden">
  <wr-iridescence
    [speed]="1"
    [amplitude]="1"
  />
</section>
```

```angular-ts
import { Component } from '@angular/core';

import { WrIridescence } from 'ngwr/bits/iridescence';

/**
 * Playground
 */
@Component({
  selector: 'iridescence-playground-example',
  templateUrl: './iridescence-playground-example.html',
  imports: [WrIridescence],
})
export class IridescencePlaygroundExample {}
```

## Behind a hero

Copy goes on a card, because text on the light default needs a card or a scrim (see Text contrast). The pointer still stirs the folds around it, and the card keeps every click.

```angular-html
<section class="hero">
  <wr-iridescence />
  <wr-card class="hero__card">
    <h3>Drape it behind the launch</h3>
    <p>The folds stay behind the card, and the button stays clickable.</p>
    <wr-btn color="primary">Get started</wr-btn>
  </wr-card>
</section>

<style>
  .hero {
    position: relative;
    display: grid;
    place-items: center;
    min-height: 22rem;
    padding: 2rem;
    overflow: hidden;
  }
  .hero__card { max-width: 26rem; text-align: center; }
</style>
```

```angular-ts
import { Component } from '@angular/core';

import { WrIridescence } from 'ngwr/bits/iridescence';
import { WrButton } from 'ngwr/button';
import { WrCard } from 'ngwr/card';

/**
 * Behind a hero
 */
@Component({
  selector: 'iridescence-behind-a-hero-example',
  templateUrl: './iridescence-behind-a-hero-example.html',
  imports: [WrIridescence, WrButton, WrCard],
})
export class IridescenceBehindAHeroExample {}
```

## Tint

`color` is multiplied over the picture, so white leaves it as it is. It takes any CSS colour, and a theme token follows the colour scheme. Pass an opaque colour: the canvas drops alpha and the stand-in keeps it.

```angular-html
<section style="position: relative; min-height: 12rem; overflow: hidden">
  <wr-iridescence color="var(--wr-color-primary)" />
</section>
```

```angular-ts
import { Component } from '@angular/core';

import { WrIridescence } from 'ngwr/bits/iridescence';

/**
 * Tint
 */
@Component({
  selector: 'iridescence-tint-example',
  templateUrl: './iridescence-tint-example.html',
  imports: [WrIridescence],
})
export class IridescenceTintExample {}
```

## Text contrast

On the default tints, `--wr-color-on-surface` holds 4.5:1 in the dark scheme and not in the light one, so text on the light default needs a scrim or a card.

| Scheme | Tint | Text | Median pixel | Worst 1% of pixels | Same, worst frame | Worst pixel |
| --- | --- | --- | --- | --- | --- | --- |
| Light | `#ffffff` | `#111827` | 8.57:1 | 3.93:1 | 2.80:1 | 2.64:1 |
| Dark | `#5c669e` | `#e9ebef` | 7.18:1 | 5.01:1 | 4.89:1 | 4.80:1 |

Measured in Chromium with a software renderer (SwiftShader) at 320×180: 240 phase combinations with the pointer at rest and 960 frames with it at the four corners, each figure the lower of the two. Worst 1% is the 1st-percentile pixel in light and the 99th in dark. Not measured: a real GPU, `prefers-contrast: more`, an `amplitude` other than 1, any other tint or text colour.

The CSS stand-in under the canvas, shown before the first frame and without WebGL2, reads 4.55:1 in light and 5.28:1 in dark at those percentiles, in a 960×540 screenshot sampled every 37th pixel.

## API

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `color` | Tint multiplied over the picture, any opaque CSS colour: the canvas drops alpha and the stand-in keeps it. Unset, empty or blank, it follows the theme. | `string \| null` | `null` |
| `speed` | Speed of the folds; 0 holds them, a negative value reverses them. Changing it never jumps them. | `number` | `1` |
| `amplitude` | How hard the pointer stirs the folds; 0 leaves the pointer out, a negative value stirs the other way. | `number` | `1` |
| `followPointer` | Whether the pointer stirs the folds at all. | `boolean` | `true` |

## CSS variables

Set these on the element itself, with a rule more specific than its Declared on selector. In a global stylesheet, put a class of yours on an ancestor it really has: `<body>` for an overlay panel, which renders outside your markup. In a component stylesheet the selector alone works, but only for an element in that component's own template. A value on a wrapper or `:root` is shadowed, because `ngwr/bits/iridescence` declares each default on its own element.

| Variable | Default | Declared on |
| --- | --- | --- |
| `--wr-iridescence-color` | `light-dark(#ffffff, #5c669e)` | `.wr-iridescence` |

## See also

- [Aurora](https://ngwr.dev/bits/aurora) — An animated aurora background for a hero or a section.
- [Threads](https://ngwr.dev/bits/threads) — Thin lines flowing behind your content like strands in a current.
- [Waves](https://ngwr.dev/bits/waves) — Drifting lines behind your content that bend around the pointer.
- [Ripple Grid](https://ngwr.dev/bits/ripple-grid) — Grid lines behind your content that the pointer sends ripples through.
- [Card](https://ngwr.dev/reference/components/card) — A surface that groups related content.
- [resolveCssColor](https://ngwr.dev/reference/utils/resolve-css-color) — Resolves a CSS colour, such as a var() or light-dark(), to a value a canvas can paint with.
