# Media
Audio playback, image carousels, QR code generation, image comparison, and content watermarking.
## Table of Contents
- [audio_player](#audio_player)
- [carousel](#carousel)
- [qr_code](#qr_code)
- [image_comparison](#image_comparison) _(new in 0.1.7)_
- [watermark](#watermark) _(new in 0.1.7)_
---
## audio_player
HTML5 audio player with play/pause, scrubber, time display, and volume control. All state is managed by the `PhiaAudioPlayer` hook.
**Hook**: `PhiaAudioPlayer`
**Attrs**: `id`, `src` (audio URL), `title`, `cover_url`
```heex
<%!-- Basic audio player --%>
<.audio_player
id="podcast-player"
src={@episode.audio_url}
title={@episode.title}
/>
<%!-- With cover art --%>
<.audio_player
id="track-player"
src={@track.url}
title={@track.name}
cover_url={@track.cover_art}
/>
<%!-- Playlist of audio players --%>
<div class="space-y-3">
<.audio_player
:for={ep <- @episodes}
id={"episode-#{ep.id}"}
src={ep.audio_url}
title={ep.title}
cover_url={ep.cover_url}
/>
</div>
```
```javascript
// app.js
import PhiaAudioPlayer from "./phia_hooks/audio_player"
// hooks: { PhiaAudioPlayer }
```
### Use cases
- Podcast episode lists
- Music streaming interfaces
- Audio message playback in chat
- Voice memo previews
---
## carousel
Touch-swipe, keyboard-navigable, looping image/content carousel.
**Hook**: `PhiaCarousel`
**Sub-components**: `carousel_content/1`, `carousel_item/1`, `carousel_previous/1`, `carousel_next/1`
**Attrs**: `id`, `loop` (bool), `orientation` (horizontal/vertical)
```heex
<%!-- Image carousel --%>
<.carousel id="product-images" loop={true}>
<.carousel_content>
<.carousel_item :for={img <- @product_images}>
<.aspect_ratio ratio="4/3" class="overflow-hidden rounded-lg">
<img src={img.url} alt={img.alt} class="object-cover w-full h-full" />
</.aspect_ratio>
</.carousel_item>
</.carousel_content>
<.carousel_previous />
<.carousel_next />
</.carousel>
<%!-- Card carousel --%>
<.carousel id="featured-posts" class="w-full">
<.carousel_content class="-ml-4">
<.carousel_item :for={post <- @featured_posts} class="pl-4 basis-1/3">
<.card>
<.card_header>
<.card_title><%= post.title %></.card_title>
</.card_header>
<.card_content>
<p class="text-sm text-muted-foreground"><%= post.excerpt %></p>
</.card_content>
</.card>
</.carousel_item>
</.carousel_content>
<.carousel_previous />
<.carousel_next />
</.carousel>
<%!-- Testimonials --%>
<.carousel id="testimonials" loop={true} class="max-w-2xl mx-auto">
<.carousel_content>
<.carousel_item :for={t <- @testimonials}>
<.card class="text-center p-8">
<blockquote class="text-lg italic">"<%= t.quote %>"</blockquote>
<div class="mt-4 flex items-center justify-center gap-3">
<.avatar size="sm"><.avatar_fallback name={t.author} /></.avatar>
<div>
<p class="font-medium text-sm"><%= t.author %></p>
<p class="text-xs text-muted-foreground"><%= t.role %></p>
</div>
</div>
</.card>
</.carousel_item>
</.carousel_content>
<.carousel_previous />
<.carousel_next />
</.carousel>
```
```javascript
// app.js
import PhiaCarousel from "./phia_hooks/carousel"
// hooks: { PhiaCarousel }
```
---
## qr_code
Server-side SVG QR code generation using `eqrcode`. No JavaScript required.
**Attrs**: `value` (string to encode), `size` (pixel width, default 200), `class`
> **Dependency**: `eqrcode` is included as a transitive dependency via PhiaUI. No extra setup needed.
```heex
<%!-- URL QR code --%>
<.qr_code value="https://phiaui.dev" />
<%!-- Custom size --%>
<.qr_code value={@share_url} size={300} class="mx-auto" />
<%!-- With label --%>
<div class="flex flex-col items-center gap-2">
<.qr_code value={@wifi_password} size={180} />
<p class="text-xs text-muted-foreground">Scan to connect to WiFi</p>
</div>
<%!-- In a modal for sharing --%>
<.dialog id="share-qr">
<:trigger>
<.button variant="outline" size="sm">
<.icon name="qr-code" size="sm" /> Share via QR
</.button>
</:trigger>
<.dialog_content>
<.dialog_header>
<.dialog_title>Scan to Share</.dialog_title>
</.dialog_header>
<div class="flex justify-center py-4">
<.qr_code value={@current_url} size={240} />
</div>
</.dialog_content>
</.dialog>
<%!-- For payment or crypto --%>
<.card class="w-fit mx-auto">
<.card_header>
<.card_title>Pay with Bitcoin</.card_title>
</.card_header>
<.card_content class="flex flex-col items-center gap-3">
<.qr_code value={@btc_address} size={200} />
<div class="flex items-center gap-2">
<code class="text-xs bg-muted px-2 py-1 rounded truncate max-w-48"><%= @btc_address %></code>
<.copy_button value={@btc_address} />
</div>
</.card_content>
</.card>
```
### Use cases
- WiFi credentials sharing
- Payment addresses (crypto, Pix, etc.)
- Contact cards / vCards
- Event check-in codes
- Share URLs on mobile
- 2FA enrollment (TOTP secrets)
---
## image_comparison
_(new in 0.1.7)_
Before/after image slider for visual comparisons. A draggable divider reveals the "after" image as it moves. Uses `PhiaImageComparison` hook.
**Hook**: `PhiaImageComparison`
**Attrs**: `id`, `before_src`, `after_src`, `before_label`, `after_label`, `initial_position` (0–100, default 50)
```heex
<.image_comparison
id="photo-compare"
before_src="/images/before.jpg"
after_src="/images/after.jpg"
before_label="Original"
after_label="Enhanced"
initial_position={40}
/>
```
### Use Cases
- Photo editing before/after
- A/B design comparisons
- Map layer toggles
- Medical image comparison
---
## watermark
_(new in 0.1.7)_
SVG tiled watermark overlay. Renders repeating diagonal text over content. Uses `:crypto.strong_rand_bytes/1` for unique pattern IDs to avoid SVG ID collisions.
**Attrs**: `text`, `opacity` (0.0–1.0, default 0.15), `angle` (degrees, default -45), `font_size` (default 14), `gap` (tile size, default 120)
```heex
<.watermark text="CONFIDENTIAL" opacity={0.1} angle={-30}>
<div class="p-8">
<h2>Sensitive Report</h2>
<p>Internal use only...</p>
</div>
</.watermark>
```
```heex
<%!-- User-specific watermark --%>
<.watermark text={@current_user.email} opacity={0.08}>
{@inner_content}
</.watermark>
```
### Use Cases
- Confidential document preview
- Draft/preview content marking
- User-traceable document exports
← [Back to README](../../README.md)