# Glossy app icon

> Hyper-real app icons drawn in SVG and CSS: rich materials, raised piping, glass that catches the light as the icon tilts.

Four app icons in the glossy, tactile style: a masked hero’s goggle on cobalt twill, a camera lens on pebbled leather, a brushed-metal dial and a stitched canvas patch. Each sits on a pale grid of blank tiles with a deep soft shadow; hover tilts it towards the pointer while the gloss and the reflections in the glass drift across it, and a press sinks it and lets it spring back. No image files: every icon is one SVG drawing lit by CSS layers.

A React component by Bruno Delorence: https://brunodelorence.com/components/glossy-app-icon. MIT License, Copyright (c) 2026 Bruno Delorence.

Category: Media.

## Anatomy

1. **Stage**: The 16:10 board the icon sits on, at least 380 px tall; the icon takes 61% of its shorter side unless `size` is set.
2. **Blank tile**: One of fourteen empty squircles the icon’s size, 12% of it apart, with the outer columns fading into the stage.
3. **Icon**: The button: it reads the pointer into `--tx` and `--ty` and tilts up to 11 degrees towards it; click, tap, Enter or Space call `onPress`.
4. **Drop shadow**: Three stacked shadows from a box 4% smaller, flat on the stage; it slides away from the tilt and tightens on a press.
5. **Face**: The 100-unit SVG drawing clipped to the squircle, under a sheen that slides with the tilt and a bevel of inset light and shade.
6. **Lens**: The raised centre of the drawing; the reflections inside its glass drift against the tilt, as if the light stayed put.
7. **Dock**: A blurred bar of 30 px icons over the bottom row: a radio group the arrow keys move through.
8. **Chosen icon**: The icon on the stage, ringed in the stage’s ink: the dock’s one tab stop.

## When to use

- A hero moment for one app or product: a landing page, a launch post, an about screen, where the icon itself is the thing to look at.
- An icon that should feel touchable on a desktop page: it leans towards the pointer and sinks when pressed, like a physical key.
- A brand mark drawn at any size from one drawing, with no image file to export, host or swap for retina screens.
- A small gallery of a few icons to compare, with the dock to switch between them on one stage.

## When not to use

- A grid of many app icons, a launcher or a settings list. Use flat images or plain SVG icons; each of these carries layered filters and shadows.
- Icons inside buttons, menus or toolbars. Use a line icon set at 16 to 24 px.
- The real icon of a shipped app in a store listing. Use the exported PNG the store asks for.
- A page that has to stay quiet and flat. The deep shadow and the gloss draw the eye; use a simple tile with a logo.
- Touch-only screens where the tilt is the point: a tap only presses it. Use a short entrance animation instead.

## Installation

Install it with the shadcn CLI, into components/glossy-app-icon of any React project:

```sh
npx shadcn@latest add https://brunodelorence.com/r/glossy-app-icon.json
```

Or copy all the source files below into one folder of your project. Either way it needs React 19 and a bundler with CSS Modules (Next.js, Vite).

It has a dark colour scheme. Its colours are CSS light-dark() pairs, kept inside @supports so that builds which rewrite light-dark() for older browsers leave them alone: it follows your page’s color-scheme (light unless the page sets one) or its colorScheme prop, with nothing to configure. Browsers without light-dark() show it light.

## Usage

```tsx
import { GlossyAppIcon } from './glossy-app-icon';

export function Launcher() {
  return (
    <GlossyAppIcon
      stage={false}
      variant="aperture"
      size={96}
      onPress={(variant) => console.log('open', variant)}
    />
  );
}
```

## States

- **Rest**: Square to the reader, lit from the top left, the shadow straight below.
- **Tilted**: `defaultTilt={{ x: 0.8, y: -0.6 }}`, as a pointer at the top right: the face leans in, the sheen and the reflections move, the shadow slides away.
- **Pressed**: `defaultPressed`: the face sinks 4.5% and darkens, the shadow tightens. A real press springs back in 460 ms.
- **Tilt off**: `tilt={false}` with the pointer at the top right: the face stays square, only the sheen and the shadow follow.
- **Icon only**: `stage={false}`: just the icon at 120 px, inline, with no tiles or dock.
- **Small**: `size={64}`: every length is a share of the side, so the bevel, sheen and shadow scale with it.
- **Light**: `colorScheme="light"`: a near-white stage and pale grey tiles.
- **Dark**: `colorScheme="dark"`: a near-black stage and a deeper shadow; the icons are drawn the same.

## How it works

- Each icon is one 100-unit SVG drawing: the material as gradients, its weave as an SVG pattern (a diagonal twill, a canvas weave, satin stitches) or as SVG noise (pebbled leather, brushed metal), and every raised line as four stacked strokes: a blurred cast shadow, the tube, its highlight and a fine glint.
- The dial’s knob is spun metal: two conic gradients under a fine repeating-conic hairline, in an HTML layer over the drawing so its highlights stay put while its orange mark sits at the level.
- HTML layers light the drawing: a bevel of inset shadows (a lit top rim, a shaded bottom, a fine dark edge), a soft-light gloss from the top left, and a drop shadow of three layers that stays flat on the stage.
- Hover tilts the icon up to 11 degrees towards the pointer. The pointer’s place goes into two custom properties, --tx and --ty, and the tilt, the gloss, the shadow, the knob and the reflections inside the glass all read them, each moving by a transform at its own depth, so the reflections slide across the lens as it turns. Nothing is redrawn.
- A press scales the face down 4.5% in 110 ms, darkens it and tightens the shadow; on release it springs back in 460 ms with an overshoot.
- The stage sizes everything from one length, --s: the icon is 61% of the stage’s shorter side, and the blank tiles are the same size, 12% of it apart, fading out at the far left and right.
- While on screen and untouched, the stage tilts the icon along a slow path, presses it, and moves on to the next icon; any pointer or key hands it to the reader, and it takes over again 5 seconds later.

## API reference

- **variant** (`'visor' | 'aperture' | 'tune' | 'patch'`, default `'visor'`): Which icon: a masked hero’s goggle, a camera lens, a brushed-metal dial or a stitched patch. On the stage, the one it starts on.
- **size** (`number`): The icon’s side in px. Without it, on the stage it takes 61% of the stage’s shorter side; alone, 120.
- **stage** (`boolean`, default `true`): Draws the stage: the grid of blank tiles and the dock of icons to pick from. Without it, just the icon.
- **label** (`string`): The icon’s accessible name; its own name by default.
- **onPress** (`(variant) => void`): Called when the icon is pressed (click, tap, Enter or Space).
- **tilt** (`boolean`, default `true`): Hover tilts the icon towards the pointer, and the reflections move with it.
- **autoplay** (`boolean`, default `true`): On the stage, while on screen and untouched, it tilts itself, presses and moves through the icons. Any input stops it.
- **colorScheme** (`'light' | 'dark' | 'system'`): Light or dark stage, or the reader’s; without it, the page’s color-scheme. The icons look the same on both.
- **defaultTilt** (`{ x: number; y: number }`): Opens tilted towards this point (-1 to 1 on each axis), as if the pointer rested there. Turns the demo off.
- **defaultPressed** (`boolean`, default `false`): Opens pressed in, as if held down. Turns the demo off.
- **className** (`string`): Classes for the root.

## Accessibility

- The icon is a real button named after it (“Visor, app icon”); Enter and Space press it, with the same sink and spring as a click, and a polite live region says it is opening.
- The dock is a radio group: the chosen icon is the one tab stop, the arrow keys move between them, and the choice is announced.
- All the drawing is decorative and hidden from screen readers; the focus ring sits outside the squircle.
- With reduced motion the icon never tilts or pops in and the demo does not play; the gloss still answers the pointer and a press still shows.
- The demo pauses off screen and in a hidden tab.

## Source

### glossy-app-icon/glossy-app-icon.tsx

```tsx
// Copyright (c) 2026 Bruno Delorence. MIT License.

'use client';

import {
  useEffect,
  useId,
  useRef,
  useState,
  type CSSProperties,
  type KeyboardEvent as ReactKeyboardEvent,
  type PointerEvent as ReactPointerEvent,
  type ReactNode,
} from 'react';
import { useAutoplay } from './use-autoplay';
import styles from './glossy-app-icon.module.css';

/*
 * Glossy app icon: a hyper-real app icon, drawn in SVG and CSS with no image file. A squircle of
 * rich material (twill fabric, pebbled leather, brushed metal, canvas) with raised piping, a thick
 * dark bezel round a glass lens, a lit rim and a deep soft shadow, on a pale grid of blank tiles.
 *
 * - Every icon is one 100-unit SVG drawing (the material, its weave as an SVG pattern, the raised
 *   lines as stacked strokes: a blurred cast shadow, the tube, its highlight) inside HTML layers
 *   that light it: a bevel of inset shadows, a gloss that slides with the tilt, the drop shadow.
 * - Hover tilts the icon towards the pointer in 3D, up to 11 degrees. Nothing is redrawn: the
 *   pointer's place goes into two custom properties (--tx, --ty, from -1 to 1) and the tilt, the
 *   gloss, the shadow and the reflections inside the glass all read them in CSS, each moving by a
 *   transform with its own depth, so the reflections drift across the glass as it turns.
 * - Press sinks the icon (scale, a tighter shadow, a darker face) and lets it spring back.
 * - The stage: the icon in the middle of a grid of blank tiles the same size, 12% apart, with a
 *   row of small icons to pick another. While on screen and untouched, it tilts itself along a slow
 *   path, presses now and then and moves on to the next icon; any input hands it to the reader.
 * - Light or dark: the stage's colours are light-dark() tokens; the icons are the same in both.
 */

export type GlossyIconVariant = 'visor' | 'aperture' | 'tune' | 'patch';

const VARIANTS: { id: GlossyIconVariant; name: string }[] = [
  { id: 'visor', name: 'Visor' },
  { id: 'aperture', name: 'Aperture' },
  { id: 'tune', name: 'Tune' },
  { id: 'patch', name: 'Patch' },
];
const nameOf = (variant: GlossyIconVariant) =>
  VARIANTS.find((item) => item.id === variant)?.name ?? variant;

/** The stage's blank tiles: columns and rows around the icon, which sits at 0, 0. */
const TILES = [-2, -1, 0, 1, 2].flatMap((column) =>
  [-1, 0, 1].map((row) => ({ column, row })).filter(({ column, row }) => column || row),
);

/**
 * The demo's path: where the tilt heads (-1 to 1 on each axis), how long it takes to get there,
 * and whether it presses on arrival. After the last stop it moves on to the next icon.
 */
const PATH: { x: number; y: number; ms: number; press?: boolean }[] = [
  { x: -0.7, y: -0.55, ms: 1100 },
  { x: 0.75, y: -0.35, ms: 1300 },
  { x: 0.6, y: 0.7, ms: 1200 },
  { x: 0, y: 0.1, ms: 900, press: true },
  { x: -0.65, y: 0.6, ms: 1200 },
  { x: 0, y: 0, ms: 1000 },
];

/* ------------------------------------------------------------------------------------------ */
/* The drawings. Each is a 100 × 100 SVG; `id` keeps its defs apart from other icons' on a page. */

/** A raised line of piping: a soft cast shadow, the tube, and the light along its top. */
function Piping({
  d,
  id,
  tube,
  shade,
  cast,
}: {
  d: string;
  id: string;
  tube: string;
  shade: string;
  cast: string;
}) {
  return (
    <g fill="none" strokeLinecap="round" strokeLinejoin="round">
      <path
        d={d}
        stroke={cast}
        strokeWidth={3.1}
        filter={`url(#${id}-soft)`}
        transform="translate(.45 .75)"
      />
      <path d={d} stroke={shade} strokeWidth={2.3} />
      <path d={d} stroke={tube} strokeWidth={1.55} transform="translate(-.22 -.32)" />
      <path
        d={d}
        stroke="#fff"
        strokeOpacity={0.85}
        strokeWidth={0.45}
        transform="translate(-.5 -.62)"
      />
    </g>
  );
}

/** The shared defs: blurs and the light falling from the top left. */
function CommonDefs({ id }: { id: string }) {
  return (
    <>
      <filter id={`${id}-soft`} x="-20%" y="-20%" width="140%" height="140%">
        <feGaussianBlur stdDeviation="0.55" />
      </filter>
      <filter id={`${id}-cast`} x="-30%" y="-30%" width="160%" height="160%">
        <feGaussianBlur stdDeviation="1.3" />
      </filter>
      <filter id={`${id}-haze`} x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="1.6" />
      </filter>
      <radialGradient id={`${id}-light`} cx="0.22" cy="0.08" r="1.05">
        <stop offset="0" stopColor="#fff" stopOpacity="0.2" />
        <stop offset="0.55" stopColor="#fff" stopOpacity="0" />
        <stop offset="1" stopColor="#000" stopOpacity="0.16" />
      </radialGradient>
    </>
  );
}

/** A diagonal twill: a fine weave of light and dark bands, rising to the right. */
function Twill({
  id,
  period = 1.8,
  dark = 0.2,
  light = 0.12,
}: {
  id: string;
  period?: number;
  dark?: number;
  light?: number;
}) {
  return (
    <pattern
      id={`${id}-twill`}
      width={period}
      height="10"
      patternUnits="userSpaceOnUse"
      patternTransform="rotate(45)"
    >
      <rect width={period * 0.48} height="10" fill="#000" fillOpacity={dark} />
      <rect x={period * 0.56} width={period * 0.3} height="10" fill="#fff" fillOpacity={light} />
    </pattern>
  );
}

// Visor: a masked hero's goggle on cobalt twill with cream piping.
const GOGGLE =
  'M20 45C20 39 24 36.5 30 36.5L70 36.5C76 36.5 80 39 80 45L80 52C80 61.5 74.5 66 66.5 66C58.5 66 55.5 58.5 50 58.5C44.5 58.5 41.5 66 33.5 66C25.5 66 20 61.5 20 52Z';
/** The suit's piping: twin racing stripes over the crown and down the chin, and a collar chevron. */
const SEAMS = [
  'M43.5 -2L44 40',
  'M56.5 -2L56 40',
  'M45 60L45.5 102',
  'M55 60L54.5 102',
  'M-2 76C18 80 34 86 50 92C66 86 82 80 102 76',
  'M-2 30C8 33 14 36 18 40',
  'M102 26C92 29 86 32 82 36',
];

function VisorArt({ id }: { id: string }) {
  return (
    <>
      <defs>
        <CommonDefs id={id} />
        <Twill id={id} />
        <linearGradient id={`${id}-base`} x1="0" y1="0" x2="0.3" y2="1">
          <stop offset="0" stopColor="#3a7cf0" />
          <stop offset="0.5" stopColor="#245bd0" />
          <stop offset="1" stopColor="#163a9a" />
        </linearGradient>
        <linearGradient
          id={`${id}-rim`}
          x1="0"
          y1="30"
          x2="0"
          y2="72"
          gradientUnits="userSpaceOnUse"
        >
          <stop offset="0" stopColor="#7d818a" />
          <stop offset="0.5" stopColor="#3a3c42" />
          <stop offset="1" stopColor="#5b5e66" />
        </linearGradient>
        <linearGradient
          id={`${id}-glass`}
          x1="0"
          y1="36"
          x2="0"
          y2="66"
          gradientUnits="userSpaceOnUse"
        >
          <stop offset="0" stopColor="#8d979f" />
          <stop offset="0.42" stopColor="#d9e3e9" />
          <stop offset="0.72" stopColor="#eef4f7" />
          <stop offset="1" stopColor="#c8d3da" />
        </linearGradient>
        <mask id={`${id}-in`} maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
          <path d={GOGGLE} fill="#fff" />
          <path d={GOGGLE} fill="none" stroke="#000" strokeWidth="7.4" strokeLinejoin="round" />
        </mask>
      </defs>
      <rect width="100" height="100" fill={`url(#${id}-base)`} />
      <rect width="100" height="100" fill={`url(#${id}-twill)`} />
      <rect width="100" height="100" fill={`url(#${id}-light)`} />
      {SEAMS.map((d) => (
        <Piping key={d} d={d} id={id} tube="#fff7e6" shade="#c8bfae" cast="rgb(6 14 60 / 0.6)" />
      ))}
      <g transform="translate(50 51) rotate(-7) scale(1.1) translate(-50 -51)" data-part="lens">
        <path
          d={GOGGLE}
          fill="#000"
          fillOpacity="0.6"
          stroke="#000"
          strokeOpacity="0.6"
          strokeWidth="7.4"
          strokeLinejoin="round"
          filter={`url(#${id}-cast)`}
          transform="translate(.9 2.1)"
        />
        <path
          d={GOGGLE}
          fill="none"
          stroke={`url(#${id}-rim)`}
          strokeWidth="7.4"
          strokeLinejoin="round"
        />
        <path d={GOGGLE} fill="none" stroke="#141519" strokeWidth="6.3" strokeLinejoin="round" />
        <path
          d={GOGGLE}
          fill="none"
          stroke="#fff"
          strokeOpacity="0.08"
          strokeWidth="2.6"
          strokeLinejoin="round"
          transform="translate(-.3 -.9)"
        />
        <g mask={`url(#${id}-in)`}>
          <path d={GOGGLE} fill={`url(#${id}-glass)`} />
          <g className={styles.reflect} data-depth="glass">
            <rect
              x="41"
              y="30"
              width="7"
              height="42"
              rx="3.5"
              fill="#fff"
              fillOpacity="0.75"
              filter={`url(#${id}-haze)`}
            />
            <rect
              x="55"
              y="30"
              width="3.5"
              height="42"
              rx="1.75"
              fill="#fff"
              fillOpacity="0.45"
              filter={`url(#${id}-haze)`}
            />
            <path
              d="M24 64L44 34L49 34L29 64Z"
              fill="#fff"
              fillOpacity="0.22"
              filter={`url(#${id}-soft)`}
            />
          </g>
          <path
            d={GOGGLE}
            fill="none"
            stroke="#000"
            strokeOpacity="0.5"
            strokeWidth="13"
            strokeLinejoin="round"
            filter={`url(#${id}-haze)`}
            transform="translate(0 1.6)"
          />
          <path d={GOGGLE} fill="none" stroke="#9aa1aa" strokeWidth="8.6" strokeLinejoin="round" />
        </g>
      </g>
    </>
  );
}

// Aperture: a camera lens on pebbled graphite leather.
function ApertureArt({ id }: { id: string }) {
  return (
    <>
      <defs>
        <CommonDefs id={id} />
        <linearGradient id={`${id}-base`} x1="0" y1="0" x2="0.25" y2="1">
          <stop offset="0" stopColor="#474a52" />
          <stop offset="0.55" stopColor="#2b2d33" />
          <stop offset="1" stopColor="#18191d" />
        </linearGradient>
        <filter id={`${id}-pebble`} x="0" y="0" width="100%" height="100%">
          <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="4" />
          <feColorMatrix values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  1.1 0 0 0 -0.42" />
        </filter>
        <linearGradient id={`${id}-metal`} x1="0" y1="0" x2="0.4" y2="1">
          <stop offset="0" stopColor="#f2f3f5" />
          <stop offset="0.45" stopColor="#9a9da4" />
          <stop offset="0.7" stopColor="#d7d9dd" />
          <stop offset="1" stopColor="#6f727a" />
        </linearGradient>
        <radialGradient id={`${id}-barrel`} cx="0.35" cy="0.25" r="0.8">
          <stop offset="0" stopColor="#3a3c42" />
          <stop offset="0.6" stopColor="#141518" />
          <stop offset="1" stopColor="#060607" />
        </radialGradient>
        <radialGradient id={`${id}-glass`} cx="0.46" cy="0.52" r="0.55">
          <stop offset="0" stopColor="#24356e" />
          <stop offset="0.45" stopColor="#101a40" />
          <stop offset="0.85" stopColor="#05070f" />
          <stop offset="1" stopColor="#020205" />
        </radialGradient>
        <clipPath id={`${id}-in`}>
          <circle cx="50" cy="53" r="23" />
        </clipPath>
        <linearGradient id={`${id}-window`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor="#5d6370" />
          <stop offset="1" stopColor="#0d0e12" />
        </linearGradient>
      </defs>
      <rect width="100" height="100" fill={`url(#${id}-base)`} />
      <rect width="100" height="100" filter={`url(#${id}-pebble)`} opacity="0.55" />
      <rect width="100" height="100" fill={`url(#${id}-light)`} />
      <rect
        x="71"
        y="11"
        width="15"
        height="9"
        rx="3"
        fill="#000"
        fillOpacity="0.5"
        filter={`url(#${id}-soft)`}
        transform="translate(.3 .7)"
      />
      <rect x="71" y="11" width="15" height="9" rx="3" fill={`url(#${id}-metal)`} />
      <rect x="72" y="12" width="13" height="7" rx="2.2" fill={`url(#${id}-window)`} />
      <path d="M73 13.5L78 13.5L76 17.5L73 17.5Z" fill="#fff" fillOpacity="0.28" />
      <g data-part="lens">
        <circle
          cx="51"
          cy="55.4"
          r="37.5"
          fill="#000"
          fillOpacity="0.7"
          filter={`url(#${id}-cast)`}
        />
        <circle cx="50" cy="53" r="37" fill={`url(#${id}-metal)`} />
        <circle
          cx="50"
          cy="53"
          r="35.6"
          fill="none"
          stroke="#1e2024"
          strokeOpacity="0.55"
          strokeWidth="2.2"
          strokeDasharray="0.7 0.7"
        />
        <circle cx="50" cy="53" r="33.6" fill={`url(#${id}-barrel)`} />
        <circle
          cx="50"
          cy="53"
          r="33.2"
          fill="none"
          stroke="#fff"
          strokeOpacity="0.18"
          strokeWidth="0.5"
        />
        <circle
          cx="50"
          cy="53"
          r="27.6"
          fill="none"
          stroke={`url(#${id}-metal)`}
          strokeWidth="1.1"
        />
        <circle cx="50" cy="53" r="26.4" fill="#050506" />
        <circle cx="50" cy="53" r="23" fill={`url(#${id}-glass)`} />
        <g clipPath={`url(#${id}-in)`}>
          <circle
            cx="50"
            cy="53"
            r="15.5"
            fill="none"
            stroke="#3a4f92"
            strokeOpacity="0.45"
            strokeWidth="0.6"
          />
          <circle cx="50" cy="53" r="9" fill="#04050a" />
          <circle
            cx="50"
            cy="53"
            r="9"
            fill="none"
            stroke="#5c70b8"
            strokeOpacity="0.4"
            strokeWidth="0.4"
          />
          <g className={styles.reflect} data-depth="glass">
            <path
              d="M33 47C36 38 44 33 53 33"
              stroke="#b35cff"
              strokeOpacity="0.75"
              strokeWidth="2.6"
              fill="none"
              strokeLinecap="round"
              filter={`url(#${id}-soft)`}
            />
            <path
              d="M68 58C66 66 60 71 52 72.5"
              stroke="#36e0a6"
              strokeOpacity="0.55"
              strokeWidth="2.2"
              fill="none"
              strokeLinecap="round"
              filter={`url(#${id}-soft)`}
            />
            <path
              d="M58 49C57 46 55 44.5 52.5 44"
              stroke="#ffb35c"
              strokeOpacity="0.6"
              strokeWidth="1"
              fill="none"
              strokeLinecap="round"
            />
            <ellipse
              cx="41"
              cy="42"
              rx="6.5"
              ry="3.6"
              transform="rotate(-38 41 42)"
              fill="#fff"
              fillOpacity="0.8"
              filter={`url(#${id}-soft)`}
            />
            <circle cx="59" cy="62" r="1.3" fill="#fff" fillOpacity="0.75" />
          </g>
        </g>
        <circle
          cx="50"
          cy="53"
          r="23"
          fill="none"
          stroke="#000"
          strokeWidth="2"
          strokeOpacity="0.8"
          filter={`url(#${id}-soft)`}
        />
      </g>
    </>
  );
}

// Tune: a brushed-metal dial with an orange level arc.
const TICKS = Array.from({ length: 25 }, (_, index) => -135 + index * 11.25);

function TuneArt({ id }: { id: string }) {
  return (
    <>
      <defs>
        <CommonDefs id={id} />
        <linearGradient id={`${id}-base`} x1="0" y1="0" x2="0.2" y2="1">
          <stop offset="0" stopColor="#f3f4f6" />
          <stop offset="0.5" stopColor="#c9ccd1" />
          <stop offset="1" stopColor="#9ea2a9" />
        </linearGradient>
        <filter id={`${id}-brush`} x="0" y="0" width="100%" height="100%">
          <feTurbulence type="fractalNoise" baseFrequency="0.008 1.4" numOctaves="2" seed="9" />
          <feColorMatrix values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0.9 0 0 0 -0.3" />
        </filter>
        <linearGradient id={`${id}-well`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor="#5c6067" />
          <stop offset="0.5" stopColor="#8c9097" />
          <stop offset="1" stopColor="#e9ebee" />
        </linearGradient>
      </defs>
      <rect width="100" height="100" fill={`url(#${id}-base)`} />
      <rect width="100" height="100" filter={`url(#${id}-brush)`} opacity="0.5" />
      <rect width="100" height="100" fill={`url(#${id}-light)`} />
      <g transform="translate(50 52)">
        {TICKS.map((angle, index) => (
          <g key={angle} transform={`rotate(${angle})`}>
            <line
              y1="-36.5"
              y2={index % 4 === 0 ? '-40.5' : '-39'}
              stroke="#fff"
              strokeOpacity="0.8"
              strokeWidth="0.9"
              transform="translate(.25 .35)"
            />
            <line
              y1="-36.5"
              y2={index % 4 === 0 ? '-40.5' : '-39'}
              stroke={index <= 16 ? '#ff6a14' : '#4a4e55'}
              strokeWidth="0.9"
              strokeLinecap="round"
            />
          </g>
        ))}
        <circle r="33.6" fill="#fff" fillOpacity="0.75" transform="translate(.3 .5)" />
        <circle r="33.4" fill={`url(#${id}-well)`} />
        <circle
          r="33.4"
          fill="none"
          stroke="#000"
          strokeOpacity="0.45"
          strokeWidth="3"
          filter={`url(#${id}-haze)`}
          transform="translate(0 1.2)"
        />
      </g>
    </>
  );
}

// Patch: an embroidered badge on olive canvas, stitched round the edge.
function PatchArt({ id }: { id: string }) {
  const bolt = 'M54.5 32L40 54L49 54L44.5 71L60.5 46.5L51.5 46.5L57 32Z';
  return (
    <>
      <defs>
        <CommonDefs id={id} />
        <linearGradient id={`${id}-base`} x1="0" y1="0" x2="0.25" y2="1">
          <stop offset="0" stopColor="#8c9748" />
          <stop offset="0.5" stopColor="#6b7533" />
          <stop offset="1" stopColor="#4a521f" />
        </linearGradient>
        <pattern id={`${id}-weave`} width="1.4" height="1.4" patternUnits="userSpaceOnUse">
          <rect width="1.4" height="0.62" fill="#000" fillOpacity="0.14" />
          <rect width="0.62" height="1.4" fill="#fff" fillOpacity="0.08" />
        </pattern>
        <pattern
          id={`${id}-satin`}
          width="0.9"
          height="10"
          patternUnits="userSpaceOnUse"
          patternTransform="rotate(-30)"
        >
          <rect width="0.4" height="10" fill="#000" fillOpacity="0.12" />
          <rect x="0.45" width="0.2" height="10" fill="#fff" fillOpacity="0.25" />
        </pattern>
        <radialGradient id={`${id}-pillow`} cx="0.38" cy="0.32" r="0.72">
          <stop offset="0" stopColor="#fbf3df" />
          <stop offset="0.7" stopColor="#ece0c2" />
          <stop offset="1" stopColor="#cfbf98" />
        </radialGradient>
        <linearGradient id={`${id}-thread`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor="#ff8a3d" />
          <stop offset="1" stopColor="#d4501a" />
        </linearGradient>
      </defs>
      <rect width="100" height="100" fill={`url(#${id}-base)`} />
      <rect width="100" height="100" fill={`url(#${id}-weave)`} />
      <rect width="100" height="100" fill={`url(#${id}-light)`} />
      <rect
        x="7.5"
        y="7.5"
        width="85"
        height="85"
        rx="17"
        fill="none"
        stroke="#1d2208"
        strokeOpacity="0.55"
        strokeWidth="1.5"
        strokeDasharray="2.6 1.7"
        strokeLinecap="round"
        filter={`url(#${id}-soft)`}
        transform="translate(.3 .5)"
      />
      <rect
        x="7.5"
        y="7.5"
        width="85"
        height="85"
        rx="17"
        fill="none"
        stroke="#f1e6c8"
        strokeWidth="1.15"
        strokeDasharray="2.6 1.7"
        strokeLinecap="round"
      />
      <g data-part="lens">
        <circle
          cx="50.8"
          cy="52"
          r="27.5"
          fill="#000"
          fillOpacity="0.6"
          filter={`url(#${id}-cast)`}
        />
        <circle cx="50" cy="50" r="27" fill="#a9461a" />
        <circle
          cx="50"
          cy="50"
          r="25.6"
          fill="none"
          stroke="#ff9a52"
          strokeWidth="2.4"
          strokeDasharray="0.42 0.32"
        />
        <circle cx="50" cy="50" r="24.2" fill={`url(#${id}-pillow)`} />
        <circle cx="50" cy="50" r="24.2" fill={`url(#${id}-satin)`} opacity="0.5" />
        <circle
          cx="50"
          cy="50"
          r="21.4"
          fill="none"
          stroke="#b9a57a"
          strokeWidth="0.55"
          strokeDasharray="1 0.8"
        />
        <path
          d={bolt}
          fill="#5a2208"
          fillOpacity="0.55"
          filter={`url(#${id}-soft)`}
          transform="translate(.5 .9)"
        />
        <path d={bolt} fill={`url(#${id}-thread)`} strokeLinejoin="round" />
        <path d={bolt} fill={`url(#${id}-satin)`} />
        <path
          d={bolt}
          fill="none"
          stroke="#ffd0a8"
          strokeOpacity="0.7"
          strokeWidth="0.45"
          strokeLinejoin="round"
          transform="translate(-.25 -.35)"
        />
      </g>
    </>
  );
}

const ART: Record<GlossyIconVariant, (props: { id: string }) => ReactNode> = {
  visor: VisorArt,
  aperture: ApertureArt,
  tune: TuneArt,
  patch: PatchArt,
};

/* ------------------------------------------------------------------------------------------ */

/** The icon itself, drawn at `--s`: the face, its lighting and its shadow. */
function IconFace({ variant }: { variant: GlossyIconVariant }) {
  const id = `gai${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
  const Art = ART[variant];
  return (
    <>
      <span className={styles.shadow} data-part="shadow" aria-hidden="true" />
      <span className={styles.tilt} data-part="tilt" aria-hidden="true">
        <span className={styles.face} data-part="face" data-variant={variant}>
          <svg
            className={styles.art}
            viewBox="0 0 100 100"
            preserveAspectRatio="none"
            focusable="false"
          >
            <Art id={id} />
          </svg>
          {variant === 'tune' && (
            <span className={styles.knob} data-part="knob">
              <span className={styles.knobMark} />
            </span>
          )}
          <span className={styles.gloss} data-part="gloss" />
          <span className={styles.bevel} data-part="bevel" />
        </span>
      </span>
    </>
  );
}

type Tilt = { x: number; y: number };

export function GlossyAppIcon({
  variant = 'visor',
  size,
  stage = true,
  label,
  onPress,
  tilt = true,
  autoplay = true,
  colorScheme,
  defaultTilt,
  defaultPressed = false,
  className = '',
}: {
  /** Which icon: a masked hero's goggle, a camera lens, a brushed dial or a stitched patch. */
  variant?: GlossyIconVariant;
  /** The icon's side in px. Without it, on the stage it takes 61% of the stage's shorter side; alone, 120. */
  size?: number;
  /** Draws the stage: the grid of blank tiles and the row of icons to pick from. Without it, just the icon. */
  stage?: boolean;
  /** The icon's accessible name; its own name by default. */
  label?: string;
  /** Called when the icon is pressed (click, tap, Enter or Space), with its variant. */
  onPress?: (variant: GlossyIconVariant) => void;
  /** Hover tilts the icon towards the pointer, and the reflections move with it. */
  tilt?: boolean;
  /** On the stage, while on screen and untouched, it tilts itself, presses and moves through the icons. */
  autoplay?: boolean;
  /** Light or dark stage, or the reader's ('system'); without it, the page's color-scheme. */
  colorScheme?: 'light' | 'dark' | 'system';
  /** Opens tilted to this point (-1 to 1 on each axis), as if the pointer were there; turns the demo off. */
  defaultTilt?: Tilt;
  /** Opens pressed in, as if held down; turns the demo off. */
  defaultPressed?: boolean;
  className?: string;
}) {
  const { ref: root, playing, take } = useAutoplay<HTMLDivElement>(5000);
  const [current, setCurrent] = useState(variant);
  const [message, setMessage] = useState('');
  const button = useRef<HTMLButtonElement>(null);
  const pressTimer = useRef(0);
  const posed = !!defaultTilt || defaultPressed;
  const demo = stage && autoplay && !posed && playing;

  // A new variant from outside wins over the reader's pick.
  const [seen, setSeen] = useState(variant);
  if (seen !== variant) {
    setSeen(variant);
    setCurrent(variant);
  }

  const announce = (text: string) => setMessage((now) => (now === text ? `${text} ` : text));

  /** Points the tilt (and everything that reads it), easing there over `ms`. */
  const aim = ({ x, y }: Tilt, ms: number) => {
    const element = button.current;
    if (!element) return;
    element.style.setProperty('--ease-ms', `${ms}ms`);
    element.style.setProperty('--tx', x.toFixed(3));
    element.style.setProperty('--ty', y.toFixed(3));
  };
  const press = (down: boolean) => {
    if (down) button.current?.setAttribute('data-pressed', '');
    else button.current?.removeAttribute('data-pressed');
  };

  // The demo: along the path, pressing where it says, then on to the next icon.
  useEffect(() => {
    if (!demo) return;
    let step = 0;
    let timer = 0;
    const next = () => {
      if (step === PATH.length) {
        step = 0;
        setCurrent(
          (now) =>
            VARIANTS[(VARIANTS.findIndex((item) => item.id === now) + 1) % VARIANTS.length].id,
        );
      }
      const stop = PATH[step++];
      aim(stop, stop.ms);
      timer = window.setTimeout(() => {
        if (stop.press) {
          press(true);
          timer = window.setTimeout(() => {
            press(false);
            timer = window.setTimeout(next, 500);
          }, 160);
        } else next();
      }, stop.ms);
    };
    next();
    return () => {
      window.clearTimeout(timer);
      press(false);
      aim({ x: 0, y: 0 }, 600);
    };
  }, [demo]);

  useEffect(() => () => window.clearTimeout(pressTimer.current), []);

  const onMove = (event: ReactPointerEvent<HTMLButtonElement>) => {
    if (!tilt) return;
    const box = event.currentTarget.getBoundingClientRect();
    const x = ((event.clientX - box.left) / box.width) * 2 - 1;
    const y = ((event.clientY - box.top) / box.height) * 2 - 1;
    aim({ x: Math.max(-1, Math.min(1, x)), y: Math.max(-1, Math.min(1, y)) }, 140);
  };
  const onLeave = () => {
    press(false);
    aim({ x: 0, y: 0 }, 650);
  };
  const activate = () => {
    onPress?.(current);
    announce(`Opening ${label ?? nameOf(current)}`);
  };
  const onKey = (event: ReactKeyboardEvent<HTMLButtonElement>) => {
    if (event.key !== ' ' && event.key !== 'Enter') return;
    if (event.repeat) return;
    press(true);
    window.clearTimeout(pressTimer.current);
    pressTimer.current = window.setTimeout(() => press(false), 160);
  };

  const pick = (next: GlossyIconVariant) => {
    setCurrent(next);
    announce(`${nameOf(next)} icon`);
  };
  const onPickerKey = (event: ReactKeyboardEvent<HTMLDivElement>) => {
    const step = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }[event.key];
    if (!step) return;
    event.preventDefault();
    const index = VARIANTS.findIndex((item) => item.id === current);
    const next = VARIANTS[(index + step + VARIANTS.length) % VARIANTS.length].id;
    pick(next);
    event.currentTarget.querySelector<HTMLButtonElement>(`[data-variant="${next}"]`)?.focus();
  };

  const icon = (
    <button
      ref={button}
      type="button"
      className={styles.icon}
      data-part="icon"
      data-tilt={tilt ? '' : undefined}
      data-pressed={defaultPressed ? '' : undefined}
      style={
        defaultTilt
          ? ({ '--tx': defaultTilt.x, '--ty': defaultTilt.y } as CSSProperties)
          : undefined
      }
      aria-label={label ?? `${nameOf(current)}, app icon`}
      onPointerMove={onMove}
      onPointerLeave={onLeave}
      onPointerDown={(event) => {
        onMove(event);
        press(true);
      }}
      onPointerUp={(event) => {
        press(false);
        if (event.pointerType === 'touch') aim({ x: 0, y: 0 }, 650);
      }}
      onPointerCancel={onLeave}
      onKeyDown={onKey}
      onClick={activate}
    >
      <IconFace key={current} variant={current} />
    </button>
  );

  const sizeStyle = size ? ({ '--s': `${size}px` } as CSSProperties) : undefined;

  if (!stage)
    return (
      <div
        ref={root}
        className={`${styles.bare} ${className}`}
        style={sizeStyle}
        data-color-scheme={colorScheme}
      >
        {icon}
        <span className={styles.status} aria-live="polite">
          {message}
        </span>
      </div>
    );

  return (
    <div
      ref={root}
      className={`${styles.root} ${className}`}
      data-color-scheme={colorScheme}
      data-part="stage"
      onPointerDownCapture={take}
      onKeyDownCapture={take}
      onPointerMove={(event) => {
        if (event.pointerType !== 'touch') take();
      }}
    >
      <div className={styles.grid} style={sizeStyle}>
        {TILES.map(({ column, row }) => (
          <span
            key={`${column} ${row}`}
            className={styles.tile}
            data-part="tile"
            aria-hidden="true"
            style={{ '--col': column, '--row': row } as CSSProperties}
          />
        ))}
        <div className={styles.slot}>{icon}</div>
      </div>
      <div
        className={styles.picker}
        data-part="picker"
        role="radiogroup"
        aria-label="Icon"
        onKeyDown={onPickerKey}
      >
        {VARIANTS.map((item) => (
          <button
            key={item.id}
            type="button"
            role="radio"
            aria-checked={item.id === current}
            aria-label={item.name}
            tabIndex={item.id === current ? 0 : -1}
            className={styles.choice}
            data-variant={item.id}
            onClick={() => pick(item.id)}
          >
            <span className={styles.mini} aria-hidden="true">
              <IconFace variant={item.id} />
            </span>
          </button>
        ))}
      </div>
      <span className={styles.status} aria-live="polite">
        {message}
      </span>
    </div>
  );
}
```

### glossy-app-icon/glossy-app-icon.module.css

```css
/* Glossy app icon (glossy-app-icon.tsx): the icon, and the stage it sits on — a pale grid of blank
   tiles the icon's size, 12% of it apart, with the icon in the middle at 61% of the stage's
   shorter side.

   Every length of the icon is a share of --s, its side, so one drawing serves every size: the
   bevel, the gloss and the shadow scale with it, and the SVG inside is 100 units wide.

   The pointer's place over the icon is --tx and --ty (-1 to 1), set from the script; the tilt,
   the gloss, the shadow and the reflections in the glass all read them, and all ease over
   --ease-ms. Only transforms and opacity change, so a hover never repaints the drawing.

   Colours are the stage's tokens, light-dark(light, dark) where the two differ: the component
   takes the color-scheme it inherits from the page, or the one its colorScheme prop sets. The
   icons themselves look the same on both. */

.root,
.bare {
  /* The stage and its tiles. */
  --bg: #fdfdfd;
  --tile: #e5e5e5;
  --tile-2: #eaeaea;
  /* The icon's drop shadow. */
  --drop: rgb(0 0 0 / 0.05);
  --drop-mid: rgb(0 0 0 / 0.24);
  --drop-far: rgb(0 0 0 / 0.24);
  /* The dock of small icons, its edge and the ring on the chosen one. */
  --dock: rgb(255 255 255 / 0.72);
  --dock-edge: rgb(0 0 0 / 0.08);
  --chosen: #1c1c1e;
  /* The same on both schemes. */
  --focus: #387bff;
  --s: 120px;
  --tx: 0;
  --ty: 0;
  -webkit-tap-highlight-color: transparent;
}
@supports (color: light-dark(#fff, #000)) {
  .root,
  .bare {
    --bg: light-dark(#fdfdfd, #111113);
    --tile: light-dark(#e5e5e5, #1f1f23);
    --tile-2: light-dark(#eaeaea, #25252a);
    --drop: light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.55));
    --drop-mid: light-dark(rgb(0 0 0 / 0.24), rgb(0 0 0 / 0.6));
    --drop-far: light-dark(rgb(0 0 0 / 0.24), rgb(0 0 0 / 0.45));
    --dock: light-dark(rgb(255 255 255 / 0.72), rgb(38 38 43 / 0.72));
    --dock-edge: light-dark(rgb(0 0 0 / 0.08), rgb(255 255 255 / 0.1));
    --chosen: light-dark(#1c1c1e, #f2f2f4);
  }
}
/* The colorScheme prop. Without it, nothing is set: color-scheme is inherited from the page. */
.root[data-color-scheme='light'],
.bare[data-color-scheme='light'] {
  color-scheme: light;
}
.root[data-color-scheme='dark'],
.bare[data-color-scheme='dark'] {
  color-scheme: dark;
}
.root[data-color-scheme='system'],
.bare[data-color-scheme='system'] {
  color-scheme: light dark;
}

/* ---- The stage ---- */

.root {
  position: relative;
  container-type: size;
  width: 100%;
  aspect-ratio: 16 / 10;
  min-height: 380px;
  max-height: 640px;
  overflow: hidden;
  overflow: clip;
  border-radius: 18px;
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--dock-edge);
}

.grid {
  position: absolute;
  inset: 0;
  --s: 61cqmin;
}
/* The tiles at the far left and right fade into the stage. */
.grid::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--bg) 92%, transparent),
    color-mix(in srgb, var(--bg) 35%, transparent) 9%,
    transparent 16% 84%,
    color-mix(in srgb, var(--bg) 35%, transparent) 91%,
    color-mix(in srgb, var(--bg) 92%, transparent)
  );
}

.tile,
.slot {
  position: absolute;
  width: var(--s);
  height: var(--s);
  left: calc(50% - var(--s) / 2 + var(--col, 0) * var(--s) * 1.12);
  top: calc(50% - var(--s) / 2 + var(--row, 0) * var(--s) * 1.12);
}
.tile {
  border-radius: 22%;
  background: linear-gradient(180deg, var(--tile), var(--tile-2));
}
.slot {
  z-index: 1;
}

/* The dock of small icons, over the bottom row of tiles. */
.picker {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 14px;
  display: flex;
  gap: 6px;
  padding: 6px;
  border-radius: 16px;
  background: var(--dock);
  box-shadow:
    inset 0 0 0 1px var(--dock-edge),
    0 6px 20px rgb(0 0 0 / 0.1);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transform: translateX(-50%);
}
.choice {
  appearance: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 11px;
  background: none;
  cursor: pointer;
  transition: box-shadow 160ms ease;
}
.choice[aria-checked='true'] {
  box-shadow: 0 0 0 2px var(--chosen);
}
.choice:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.mini {
  --s: 30px;
  position: relative;
  display: block;
  width: var(--s);
  height: var(--s);
}

.status {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---- Without the stage ---- */

.bare {
  position: relative;
  display: inline-block;
  width: var(--s);
  height: var(--s);
}

/* ---- The icon ---- */

.icon {
  --press: 0;
  appearance: none;
  position: relative;
  display: block;
  width: var(--s);
  height: var(--s);
  padding: 0;
  border: 0;
  border-radius: 24%;
  background: none;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.icon:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: calc(var(--s) * 0.04);
}
.icon[data-pressed] {
  --press: 1;
}

/* The shadow stays flat on the stage: it slides away from the tilt, and tightens on a press. It is
   cast by a box 4% smaller, spread back out to the icon's size, so a tilted face never shows its
   empty middle. */
.shadow {
  position: absolute;
  inset: 4%;
  border-radius: 22%;
  box-shadow:
    0 calc(var(--s) * 0.004) calc(var(--s) * 0.012) calc(var(--s) * 0.04) var(--drop),
    0 calc(var(--s) * 0.045) calc(var(--s) * 0.08) calc(var(--s) * 0.04) var(--drop-mid),
    0 calc(var(--s) * 0.085) calc(var(--s) * 0.17) calc(var(--s) * 0.04) var(--drop-far);
  transform: translate(calc(var(--tx) * var(--s) * -0.025), calc(var(--ty) * var(--s) * -0.02))
    scale(calc(1 - var(--press) * 0.05));
  opacity: calc(1 - var(--press) * 0.35);
  transition:
    transform var(--ease-ms, 600ms) cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 160ms ease;
}

.tilt {
  position: absolute;
  inset: 0;
  display: block;
  transform: perspective(calc(var(--s) * 3.6)) rotateX(calc(var(--ty) * -11deg))
    rotateY(calc(var(--tx) * 11deg));
  transition: transform var(--ease-ms, 600ms) cubic-bezier(0.22, 0.61, 0.36, 1);
  animation: arrive 560ms cubic-bezier(0.34, 1.5, 0.64, 1) both;
}
.icon:not([data-tilt]) .tilt {
  transform: none;
}
@keyframes arrive {
  from {
    opacity: 0;
    scale: 0.86;
  }
}

/* The face: the drawing and its light, clipped to the squircle. A press sinks it; it springs back. */
.face {
  position: absolute;
  inset: 0;
  display: block;
  overflow: hidden;
  border-radius: 24%;
  transform: scale(calc(1 - var(--press) * 0.045));
  transition: transform 460ms cubic-bezier(0.34, 1.8, 0.5, 1);
}
.icon[data-pressed] .face {
  transition-duration: 110ms;
  transition-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1);
}

.art {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* The reflections in the glass slide against the tilt, as if the light stayed put. */
.reflect {
  transform: translate(calc(var(--tx) * -3.5px), calc(var(--ty) * -3px));
  transition: transform var(--ease-ms, 600ms) cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The rim: lit along the top, shaded along the bottom, with a fine dark edge. A press darkens it. */
.bevel {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgb(0 0 0 / calc(var(--press) * 0.1));
  box-shadow:
    inset 0 calc(var(--s) * 0.008) calc(var(--s) * 0.004) rgb(255 255 255 / 0.42),
    inset calc(var(--s) * 0.004) 0 calc(var(--s) * 0.006) rgb(255 255 255 / 0.12),
    inset 0 calc(var(--s) * -0.01) calc(var(--s) * 0.012) rgb(0 0 0 / 0.3),
    inset 0 0 0 calc(var(--s) * 0.0025) rgb(0 0 0 / 0.25),
    inset 0 0 calc(var(--s) * 0.05) rgb(0 0 0 / 0.1);
  transition: background-color 160ms ease;
}

/* The gloss: a soft sheen from the top left that slides as the icon turns. */
.gloss {
  position: absolute;
  inset: -25%;
  background:
    radial-gradient(
      closest-side at 36% 30%,
      rgb(255 255 255 / 0.32),
      rgb(255 255 255 / 0.1) 55%,
      transparent
    ),
    linear-gradient(180deg, rgb(255 255 255 / 0.1), transparent 40%);
  mix-blend-mode: soft-light;
  transform: translate(calc(var(--tx) * 12%), calc(var(--ty) * 10%));
  transition: transform var(--ease-ms, 600ms) cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Tune's knob: spun metal, raised off the dial, with its orange mark at the level. */
.knob {
  position: absolute;
  left: 21%;
  top: 23%;
  width: 58%;
  height: 58%;
  border-radius: 50%;
  background:
    repeating-conic-gradient(rgb(0 0 0 / 0.045) 0deg 0.7deg, rgb(255 255 255 / 0.05) 0.7deg 1.4deg),
    conic-gradient(
      from 15deg,
      #fbfcfd,
      #a4a8ae 11%,
      #eceef0 24%,
      #8b8f96 36%,
      #f8f9fa 50%,
      #9a9ea5 62%,
      #edeff1 74%,
      #8c9097 87%,
      #fbfcfd
    );
  box-shadow:
    0 calc(var(--s) * 0.022) calc(var(--s) * 0.03) rgb(0 0 0 / 0.42),
    0 calc(var(--s) * 0.005) calc(var(--s) * 0.006) rgb(0 0 0 / 0.3),
    inset 0 0 0 calc(var(--s) * 0.004) rgb(255 255 255 / 0.7),
    inset 0 calc(var(--s) * -0.008) calc(var(--s) * 0.012) rgb(0 0 0 / 0.22);
  transform: translate(calc(var(--tx) * var(--s) * -0.012), calc(var(--ty) * var(--s) * -0.01));
  transition: transform var(--ease-ms, 600ms) cubic-bezier(0.22, 0.61, 0.36, 1);
}
.knob::before {
  content: '';
  position: absolute;
  inset: 9%;
  border-radius: 50%;
  background:
    repeating-conic-gradient(rgb(0 0 0 / 0.05) 0deg 0.5deg, rgb(255 255 255 / 0.06) 0.5deg 1deg),
    conic-gradient(
      from 195deg,
      #f4f5f7,
      #9fa3aa 12%,
      #e9ebee 25%,
      #8f939a 38%,
      #f6f7f8 50%,
      #a0a4ab 63%,
      #eaecef 75%,
      #8e9299 88%,
      #f4f5f7
    );
  box-shadow:
    inset 0 calc(var(--s) * 0.004) calc(var(--s) * 0.005) rgb(0 0 0 / 0.25),
    0 calc(var(--s) * 0.003) 0 rgb(255 255 255 / 0.6);
}
.knobMark {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 7%;
  height: 15%;
  border-radius: 999px;
  background: linear-gradient(180deg, #ff9a4a, #f05a0e);
  box-shadow:
    inset 0 calc(var(--s) * 0.003) calc(var(--s) * 0.003) rgb(120 30 0 / 0.55),
    0 0 calc(var(--s) * 0.02) rgb(255 110 20 / 0.55);
  transform: translate(-50%, -50%) rotate(45deg) translateY(-215%);
}

/* The small icons in the dock hold still. */
.mini .tilt {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .tilt {
    animation: none;
    transform: none;
  }
  .face {
    transition-duration: 1ms;
  }
}
```

### glossy-app-icon/use-autoplay.ts

```tsx
'use client';

import { useCallback, useEffect, useRef, useState } from 'react';

/*
 * When a demo may play on its own: while part of it is on screen and the tab is visible, never
 * under reduced motion, and not while the reader is using it. `take()` hands it to the reader; it
 * takes back over `idle` ms after their last input.
 */
export function useAutoplay<T extends HTMLElement>(idle = 6000) {
  const ref = useRef<T>(null);
  const [visible, setVisible] = useState(false);
  const [reduced, setReduced] = useState(true);
  const [taken, setTaken] = useState(false);
  const timer = useRef(0);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;
    let onScreen = false;
    const update = () => setVisible(onScreen && !document.hidden);
    const observer = new IntersectionObserver(
      ([entry]) => {
        onScreen = entry.isIntersecting;
        update();
      },
      { threshold: 0.3 },
    );
    observer.observe(element);
    document.addEventListener('visibilitychange', update);
    const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
    const readMotion = () => setReduced(motion.matches);
    readMotion();
    motion.addEventListener('change', readMotion);
    return () => {
      observer.disconnect();
      document.removeEventListener('visibilitychange', update);
      motion.removeEventListener('change', readMotion);
    };
  }, []);

  const take = useCallback(() => {
    setTaken(true);
    window.clearTimeout(timer.current);
    timer.current = window.setTimeout(() => setTaken(false), idle);
  }, [idle]);
  useEffect(() => () => window.clearTimeout(timer.current), []);

  return { ref, take, playing: visible && !reduced && !taken };
}
```