# Profile Card

> A framed profile card that tilts toward the cursor, with holographic foil shining through a tiled pattern.

A glassy frame holds a portrait tile and a footer with handle, status and a contact button. Move the cursor over the card and it tilts toward it on a spring, the portrait shifts for parallax, a glare follows the light, and holographic foil lights up only through the tiled `icon-url` pattern. The card catches the light once as it appears, then settles. Tune the foil, glare, tilt and radius in the panel.

<demo-profile-card>



</demo-profile-card>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import ProfileCard from '~/components/ui/profile-card/ProfileCard.vue';

  function onContactClick() {
    console.log('contact');
  }
</script>

<template>
  <ProfileCard
    avatar-url="/images/portrait.webp"
    icon-url="/images/icon-pattern.png"
    name="Javi A. Torres"
    title="Software Engineer"
    handle="javicodes"
    status="Online"
    theme="dark"
    @contact-click="onContactClick"
  />
</template>
```

`icon-url` is used as a CSS mask, so only its alpha channel matters: a white logo pattern on a transparent background works best. Without it the foil covers the whole tile at lower strength.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        avatar-url
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Portrait shown on the card
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        icon-url
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Tileable pattern, like a logo pattern. The holo foil shows only through it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mini-avatar-url
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Small round image next to the handle. Hidden when not set
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        name
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Javi A. Torres'
      </code>
    </td>
    
    <td>
      Name shown over the portrait
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Software Engineer'
      </code>
    </td>
    
    <td>
      Role shown under the name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        handle
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'javicodes'
      </code>
    </td>
    
    <td>
      Handle shown in the footer, without the @
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Online'
      </code>
    </td>
    
    <td>
      Status text shown under the handle
    </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>
        contact-text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Contact'
      </code>
    </td>
    
    <td>
      Label of the contact button. Empty hides the button
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        show-user-info
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Shows the footer with the handle, status and contact button
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light'
      </code>
    </td>
    
    <td>
      <code>
        'dark'
      </code>
    </td>
    
    <td>
      Color theme of the frame and footer
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        backdrop-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Color behind the portrait. Shows around cut-out photos. Uses the theme color if unset
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        16
      </code>
    </td>
    
    <td>
      Corner radius of the photo, in px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        holo
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.8
      </code>
    </td>
    
    <td>
      Strength of the holographic foil, 0–1
    </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, in degrees
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        parallax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        8
      </code>
    </td>
    
    <td>
      How far the portrait shifts as the card tilts, in px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        enable-mobile-tilt
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Tilts the card with the motion of the phone after a tap. Needs HTTPS
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        mobile-tilt-sensitivity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        5
      </code>
    </td>
    
    <td>
      How strongly phone motion tilts the card
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Catches the light once as the card appears, then settles
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes on the root
    </td>
  </tr>
</tbody>
</table>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        contact-click
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Emitted when the contact button is clicked
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The contact button is a real `<button>` with a visible focus ring and an `aria-label` that includes the name.
- Touch input tilts the card only while a finger is down, so vertical page scrolling keeps working (`touch-action: pan-y`).
- With `prefers-reduced-motion: reduce` the card does not tilt, the portrait does not shift and the intro is skipped; the light still follows the cursor.
- The spring loop runs only while the card is moving and stops once it settles.
- Ported from [React Bits](https://reactbits.dev/components/profile-card) by David Haz.
