# Reflective Card

> A satin metal ID card that reflects a looping video or your camera, tilts toward the cursor and catches a sweeping light.

A brushed-metal ID card rendered with WebGL2. It reflects a looping video (or a soft studio when there is none), switches to your mirrored camera when `webcam` is on, tilts toward the cursor or finger and catches a light that follows the pointer. Move over the card to tilt it; use the panel to try presets, the camera and the surface controls.

<demo-reflective-card>



</demo-reflective-card>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/reflective-card.json"
```

## Usage

```vue
<script setup lang="ts">
  import { ref } from 'vue';
  import ReflectiveCard from '~/components/ui/reflective-card/ReflectiveCard.vue';

  const cameraOn = ref(false);

  function onWebcamError(error: Error) {
    console.warn(error.message);
    cameraOn.value = false;
  }
</script>

<template>
  <ReflectiveCard
    name="Alexander Doe"
    role="Senior Developer"
    theme="dark"
    video-src="/videos/reflection.mp4"
    :webcam="cameraOn"
    tint="#f3cf8a"
    :roughness="0.3"
    :blur-strength="6"
    @webcam-error="onWebcamError"
  />
</template>
```

The video must be same-origin or served with CORS headers, because its frames are uploaded to a WebGL texture. Without `video-src` the card reflects a soft studio light.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        name
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Alexander Doe'
      </code>
    </td>
    
    <td>
      Name shown on the card
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        role
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Senior Developer'
      </code>
    </td>
    
    <td>
      Role shown under the name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        badge
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Verified'
      </code>
    </td>
    
    <td>
      Label of the badge in the top left corner
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Active'
      </code>
    </td>
    
    <td>
      Label of the status chip in the top right corner
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#22c55e'
      </code>
    </td>
    
    <td>
      Color of the status dot
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        id-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'ID number'
      </code>
    </td>
    
    <td>
      Small label above the ID number
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        id-value
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '8901 2345 6789'
      </code>
    </td>
    
    <td>
      ID number shown in the footer
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light'
      </code>
    </td>
    
    <td>
      <code>
        'dark'
      </code>
    </td>
    
    <td>
      Dark graphite or light silver metal, with matching text
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        video-src
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Looping video the card reflects when the camera is off
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        webcam
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Reflects the mirrored camera feed. Asks for access and falls back to the video
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tint
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#ffffff'
      </code>
    </td>
    
    <td>
      Color of the metal. White gives plain steel
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        metalness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      How strongly the surface mirrors the scene, 0–1. At 0 the card turns matte
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        roughness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.3
      </code>
    </td>
    
    <td>
      Brushed grain, 0–1. Stretches reflections along the grain
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blur-strength
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      Blur of the reflection (px)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        grayscale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Removes color from the reflection, 0–1
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        warp
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      Gentle bends in the surface that ripple the reflection
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        warp-scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Size of the bends
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shine
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Brightness of the light band that sweeps across the card as it tilts
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bevel
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Width of the polished edge that catches the light (px)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        glare
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Strength of the light that follows the cursor, 0–1
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        enable-tilt
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Tilts the card toward the cursor
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt-strength
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        12
      </code>
    </td>
    
    <td>
      Largest tilt angle (degrees)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Fades the card in and sweeps the light across it once
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        width
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        320
      </code>
    </td>
    
    <td>
      Width of the card. Numbers are px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        height
      </code>
    </td>
    
    <td>
      <code>
        number | string
      </code>
    </td>
    
    <td>
      <code>
        500
      </code>
    </td>
    
    <td>
      Height of the card. Numbers are px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        24
      </code>
    </td>
    
    <td>
      Corner radius (px)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the card
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        webcam-error
      </code>
    </td>
    
    <td>
      <code>
        (error: Error)
      </code>
    </td>
    
    <td>
      Fired when camera access is denied or unavailable
    </td>
  </tr>
</tbody>
</table>

## Slots

<table>
<thead>
  <tr>
    <th>
      Slot
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        default
      </code>
    </td>
    
    <td>
      Custom card content. Replaces the badge, name and footer
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The canvas and the hidden video elements are `aria-hidden`; the card text stays real, selectable-by-screen-reader DOM.
- With `prefers-reduced-motion: reduce` the card does not tilt, skips the intro and does not autoplay the video.
- The camera is only requested when `webcam` is `true`, and the stream is stopped when it turns off or the card unmounts.
- Ported from [React Bits](https://reactbits.dev/components/reflective-card) by David Haz.
