spaceui
blockblock-eleven-labssource: ok · refreshed
Eleven Labs Landing
ElevenLabs-inspired landing page block with a live audio-reactive orb hero.
npx shadcn add "https://www.spaceui.one/r/block-eleven-labs.json"free
Where it goes
hero
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · light@elevenlabs/elevenlabs-js@tabler/icons-react
Registry dependencies
https://www.spaceui.one/r/components-orb-smooth.jsonhttps://www.spaceui.one/r/components-shader-silk-gradient.jsonhttps://www.spaceui.one/r/lib-utils.json
Files15 · source cached 10/8/2026, 9:25:38 AM
src/registry/blocks/eleven-labs/eleven-labs-1/index.tsx
'use client'
import { ProductTabsSection } from './sections/product-tabs'
import { CreativePlatformSection } from './sections/creative-platform'
export default function ElevenLabsPage() {
return (
<main className="size-full pb-12 bg-[#fdfcfc] [font-family:var(--font-eleven-inter)] text-[17px] leading-[1.4] text-black antialiased">
<ProductTabsSection />
<CreativePlatformSection />
</main>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/components/code-controls.tsx
'use client'
import { useCallback, useEffect, useId, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { cn } from '@/lib/utils'
import { IconCheck, IconCopy } from '@tabler/icons-react'
import { focus } from './shared'
import { SelectChevron } from './speech-select'
export function CopyIcon({ copied }: { copied: boolean }) {
return copied ? (
<IconCheck aria-hidden="true" strokeWidth={1.5} className="size-4" />
) : (
<IconCopy aria-hidden="true" strokeWidth={1.5} className="size-4" />
)
}
export function ApiPromptInput({ value, onChange }: { value: string; onChange: (value: string) => void }) {
return (
<span className="group relative flex h-6 min-w-0 flex-auto self-center rounded-lg bg-[#eef6ff]/70 ring-[.5px] ring-[#78b7ff] ring-inset transition hover:ring-[#368cff] focus-within:ring-1 focus-within:ring-[#368cff]">
<input
type="text"
value={value}
onChange={(event) => onChange(event.target.value)}
placeholder=" "
spellCheck={false}
maxLength={500}
aria-label="Prompt"
className="h-6 w-full min-w-0 bg-transparent px-1.5 text-[#052f70] caret-[#368cff] outline-none"
/>
<span className="pointer-events-none absolute inset-y-px left-px w-7 rounded-l-lg bg-gradient-to-r from-[#eef6ff] to-transparent opacity-0 transition-opacity group-focus-within:opacity-100" />
<span className="pointer-events-none absolute inset-y-px right-px w-7 rounded-r-lg bg-gradient-to-l from-[#eef6ff] to-transparent opacity-0 transition-opacity group-focus-within:opacity-100" />
</span>
)
}
export function CodeLanguageSelect({
value,
onChange,
}: {
value: 'ts' | 'python'
onChange: (value: 'ts' | 'python') => void
}) {
const [open, setOpen] = useState(false)
const [menuPosition, setMenuPosition] = useState({ left: 0, top: 0 })
const triggerRef = useRef<HTMLButtonElement>(null)
const menuRef = useRef<HTMLDivElement>(null)
const listboxId = useId()
const options = [
{ id: 'ts', label: 'TypeScript' },
{ id: 'python', label: 'Python' },
] as const
const updatePosition = useCallback(() => {
const trigger = triggerRef.current
if (!trigger) return
const rect = trigger.getBoundingClientRect()
setMenuPosition({ left: rect.left, top: rect.bottom - 2 })
}, [])
useEffect(() => {
if (!open) return
updatePosition()
const closeOnOutsidePress = (event: MouseEvent) => {
const target = event.target as Node
if (!triggerRef.current?.contains(target) && !menuRef.current?.contains(target)) {
setOpen(false)
}
}
const closeOnEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setOpen(false)
triggerRef.current?.focus()
}
}
document.addEventListener('mousedown', closeOnOutsidePress)
document.addEventListener('keydown', closeOnEscape)
window.addEventListener('resize', updatePosition)
window.addEventListener('scroll', updatePosition, true)
return () => {
document.removeEventListener('mousedown', closeOnOutsidePress)
document.removeEventListener('keydown', closeOnEscape)
window.removeEventListener('resize', updatePosition)
window.removeEventListener('scroll', updatePosition, true)
}
}, [open, updatePosition])
const menu =
open && typeof document !== 'undefined'
? createPortal(
<div
ref={menuRef}
id={listboxId}
role="listbox"
aria-label="Code language"
className="fixed z-[220] w-[138px] rounded-2xl bg-white p-1.5 text-[15px] leading-6 text-black shadow-[0_0_1px_rgba(0,0,0,.4),0_1px_1px_rgba(0,0,0,.04),0_8px_24px_rgba(0,0,0,.10)] ring-[.5px] ring-black/[.075] outline-none"
style={menuPosition}
>
{options.map((option) => {
const selected = option.id === value
return (
<button
key={option.id}
type="button"
role="option"
aria-selected={selected}
onClick={() => {
onChange(option.id)
setOpen(false)
triggerRef.current?.focus()
}}
className="flex h-9 w-full items-center rounded-[10px] px-2.5 text-left outline-none transition-colors hover:bg-[#f5f3f1] focus-visible:bg-[#f5f3f1]"
>
<span className="min-w-0 flex-1 truncate">{option.label}</span>
{selected && <IconCheck aria-hidden="true" strokeWidth={1.5} className="size-4 flex-none" />}
</button>
)
})}
</div>,
document.body,
)
: null
return (
<>
<button
ref={triggerRef}
type="button"
aria-label="Code language"
aria-haspopup="listbox"
aria-expanded={open}
aria-controls={open ? listboxId : undefined}
onClick={() => {
updatePosition()
setOpen((current) => !current)
}}
className={cn(
'flex h-9 items-center gap-2 rounded-xl pr-2 pl-3 text-[14px] leading-5 font-medium text-black outline-none transition-colors',
focus,
)}
>
<span>{value === 'ts' ? 'TypeScript' : 'Python'}</span>
<span className="transition-transform">
<SelectChevron />
</span>
</button>
{menu}
</>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/components/shared.tsx
'use client'
import type { CSSProperties } from 'react'
import { cn } from '@/lib/utils'
import {
IconChevronLeft,
IconChevronRight,
IconLoader2,
IconPlayerPauseFilled,
IconPlayerPlayFilled,
} from '@tabler/icons-react'
import { voices } from '../data'
export type OrbStyle = CSSProperties & {
'--orb-x': string
'--orb-mobile-x': string
'--orb-scale': number
'--orb-mobile-scale': number
}
export type VoiceDetailStyle = CSSProperties & {
'--detail-x': string
}
export const focus =
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#266df0]'
export const buttonShadow = 'shadow-[0_0_1px_rgb(0_0_0_/_0.4),0_1px_1px_rgb(0_0_0_/_0.04),0_2px_4px_rgb(0_0_0_/_0.04)]'
export function Chevron({ direction }: { direction: 'previous' | 'next' }) {
return direction === 'previous' ? (
<IconChevronLeft aria-hidden="true" strokeWidth={1.5} className="size-5" />
) : (
<IconChevronRight aria-hidden="true" strokeWidth={1.5} className="size-5" />
)
}
export function PlayIcon({ playing }: { playing: boolean }) {
if (playing) {
return <IconPlayerPauseFilled aria-hidden="true" className="size-5" />
}
return <IconPlayerPlayFilled aria-hidden="true" className="size-5" />
}
export function SpeechLoadingSpinner() {
return (
<IconLoader2
aria-hidden="true"
strokeWidth={2.25}
className="size-4 animate-spin"
style={{ animationDuration: '0.7s' }}
/>
)
}
export function VoiceDetails({ voiceIndex, muted = false }: { voiceIndex: number; muted?: boolean }) {
const voice = voices[voiceIndex]
return (
<div className="flex h-20 min-w-0 flex-col items-center justify-center gap-2 text-center">
<span
className={cn(
'inline-flex items-center rounded-sm text-[16px] leading-6',
muted ? 'text-[#777169]' : 'text-black',
)}
>
{voice.category}
</span>
<p
className={cn(
'm-0 max-w-[216px] text-[13px] leading-[18px] text-balance',
muted ? 'text-[#a9a49e]' : 'text-[#777169]',
)}
>
{voice.description}
</p>
</div>
)
}
export function CompactPlayButton({
playing,
loading = false,
onClick,
iconOnly = false,
}: {
playing: boolean
loading?: boolean
onClick: () => void
iconOnly?: boolean
}) {
return (
<button
type="button"
aria-label={playing ? 'Pause' : 'Play'}
disabled={loading}
onClick={onClick}
className={cn(
'flex flex-none items-center justify-center rounded-full bg-black text-white transition hover:bg-[#59544f] disabled:cursor-default',
focus,
iconOnly || loading ? 'size-9' : 'h-9 px-3.5 text-[15px] leading-none',
)}
>
{loading ? (
<SpeechLoadingSpinner />
) : iconOnly ? (
<span className="[&>svg]:!size-4">
<PlayIcon playing={playing} />
</span>
) : playing ? (
'Pause'
) : (
'Play'
)}
</button>
)
}
export function SectionDivider() {
return (
<div className="relative mx-auto h-1 max-w-[1304px] border-x border-black/[.075] bg-black/[.075]">
<span className="absolute top-1/2 left-0 z-20 flex size-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-[#fdfcfc]">
<i className="size-0.5 rounded-full bg-black" />
</span>
<span className="absolute top-1/2 right-0 z-20 flex size-5 translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-[#fdfcfc]">
<i className="size-0.5 rounded-full bg-black" />
</span>
</div>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/components/speech-select.tsx
'use client'
import { useCallback, useEffect, useId, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { cn } from '@/lib/utils'
import { IconCheck, IconChevronDown } from '@tabler/icons-react'
import { focus } from './shared'
export function SelectChevron() {
return <IconChevronDown aria-hidden="true" strokeWidth={2} className="size-4 flex-none text-[#a9a49e]" />
}
export function InlineSelectChevron() {
return <IconChevronDown aria-hidden="true" strokeWidth={1.5} className="ml-0.5 size-4 flex-none" />
}
export type SpeechSelectItem = {
id: string
label: string
description?: string
image: string
}
export function SpeechSelect({
ariaLabel,
value,
items,
onChange,
menuWidth,
hideTriggerImageOnMobile = false,
hideTriggerLabelOnMobile = false,
inlineCode = false,
triggerValue,
}: {
ariaLabel: string
value: string
items: readonly SpeechSelectItem[]
onChange: (value: string) => void
menuWidth: number
hideTriggerImageOnMobile?: boolean
hideTriggerLabelOnMobile?: boolean
inlineCode?: boolean
triggerValue?: string
}) {
const [open, setOpen] = useState(false)
const [menuPosition, setMenuPosition] = useState({ left: 0, top: 0 })
const triggerRef = useRef<HTMLButtonElement>(null)
const menuRef = useRef<HTMLDivElement>(null)
const listboxId = useId()
const selectedItem = items.find((item) => item.id === value) ?? items[0]
const updateMenuPosition = useCallback(() => {
const trigger = triggerRef.current
if (!trigger) return
const rect = trigger.getBoundingClientRect()
const edge = 6
setMenuPosition({
left: Math.max(edge, Math.min(rect.left - 4, window.innerWidth - menuWidth - edge)),
top: rect.bottom,
})
}, [menuWidth])
const toggleMenu = () => {
if (!open) updateMenuPosition()
setOpen((current) => !current)
}
useEffect(() => {
if (!open) return
const closeFromOutside = (event: PointerEvent) => {
const target = event.target as Node
if (!triggerRef.current?.contains(target) && !menuRef.current?.contains(target)) {
setOpen(false)
}
}
const closeFromEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setOpen(false)
triggerRef.current?.focus()
}
}
const reposition = () => updateMenuPosition()
document.addEventListener('pointerdown', closeFromOutside)
document.addEventListener('keydown', closeFromEscape)
window.addEventListener('resize', reposition)
window.addEventListener('scroll', reposition, true)
const selectedOption = menuRef.current?.querySelector<HTMLElement>('[role="option"][aria-selected="true"]')
selectedOption?.scrollIntoView({ block: 'nearest' })
return () => {
document.removeEventListener('pointerdown', closeFromOutside)
document.removeEventListener('keydown', closeFromEscape)
window.removeEventListener('resize', reposition)
window.removeEventListener('scroll', reposition, true)
}
}, [open, updateMenuPosition])
const menu = open
? createPortal(
<div
ref={menuRef}
id={listboxId}
role="listbox"
aria-label={ariaLabel}
className="fixed z-[220] max-h-[206px] overflow-auto rounded-2xl bg-white p-1.5 text-[15px] leading-[22px] text-black outline-none [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
style={{
left: menuPosition.left,
top: menuPosition.top,
width: menuWidth,
boxShadow: 'rgba(0, 0, 0, 0.4) 0px 0px 1px 0px, rgba(0, 0, 0, 0.04) 0px 4px 4px 0px',
}}
onKeyDown={(event) => {
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return
event.preventDefault()
const options = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>('[role="option"]'))
const current = options.indexOf(document.activeElement as HTMLButtonElement)
const direction = event.key === 'ArrowDown' ? 1 : -1
const next =
current === -1
? options.findIndex((option) => option.getAttribute('aria-selected') === 'true')
: (current + direction + options.length) % options.length
options[Math.max(0, next)]?.focus()
}}
>
{items.map((item) => {
const selected = item.id === value
return (
<button
key={item.id}
type="button"
role="option"
aria-selected={selected}
onClick={() => {
onChange(item.id)
setOpen(false)
triggerRef.current?.focus()
}}
className={cn(
'relative flex h-9 w-full items-center gap-2 rounded-xl pl-2.5 text-left outline-none transition-colors before:pointer-events-none before:absolute before:inset-0 before:rounded-xl before:bg-[#f5f3f1] before:opacity-0 before:transition-opacity hover:before:opacity-100 focus-visible:before:opacity-100',
selected ? 'pr-1.5' : 'pr-[38px]',
)}
>
<span
aria-hidden="true"
className="relative z-10 size-4 flex-none self-center rounded-full bg-[#dedbd8] bg-cover bg-center"
style={{ backgroundImage: `url("${item.image}")` }}
/>
<span className="relative z-10 min-w-0 truncate">{item.label}</span>
{item.description ? (
<span className="relative z-10 ml-1 flex-none text-[13px] leading-[18px] text-[#777169]">
{item.description}
</span>
) : null}
{selected ? (
<IconCheck
aria-hidden="true"
strokeWidth={1.5}
className="relative z-10 ml-auto size-6 flex-none text-black"
/>
) : null}
</button>
)
})}
</div>,
document.body,
)
: null
return (
<span className={inlineCode ? 'inline' : 'block min-w-0'}>
<button
ref={triggerRef}
type="button"
aria-label={ariaLabel}
aria-haspopup="listbox"
aria-expanded={open}
aria-controls={open ? listboxId : undefined}
onClick={toggleMenu}
className={
inlineCode
? cn(
'inline-flex h-[23px] max-w-[230px] items-center rounded-lg bg-[#eef6ff]/70 pr-[3px] pl-1 align-middle text-[13px] leading-none font-normal text-[#052f70] ring-[.5px] ring-[#78b7ff] ring-inset outline-none transition hover:ring-1 hover:ring-[#368cff]',
focus,
)
: cn(
'flex h-9 w-full items-center gap-2 rounded-xl pr-2 pl-3 text-[15px] leading-[22px] font-medium text-black outline-none transition-colors',
focus,
)
}
>
<span className={cn('flex min-w-0 flex-1 items-baseline', !inlineCode && 'gap-2')}>
{!inlineCode && (
<span
aria-hidden="true"
className={cn(
'size-4 flex-none self-center rounded-full bg-[#dedbd8] bg-cover bg-center',
hideTriggerImageOnMobile && 'max-sm:hidden',
)}
style={{ backgroundImage: `url("${selectedItem.image}")` }}
/>
)}
<span className={cn('min-w-0 truncate', hideTriggerLabelOnMobile && 'max-sm:hidden')}>
{triggerValue ?? selectedItem.label}
</span>
</span>
{inlineCode ? (
<InlineSelectChevron />
) : (
<span className="flex-none transition-transform">
<SelectChevron />
</span>
)}
</button>
{menu}
</span>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/data/flags.ts
export const speechLanguages = [
['en', 'English'],
['af', 'Afrikaans'],
['ar', 'Arabic'],
['hy', 'Armenian'],
['as', 'Assamese'],
['az', 'Azerbaijani'],
['be', 'Belarusian'],
['bn', 'Bengali'],
['bs', 'Bosnian'],
['bg', 'Bulgarian'],
['ca', 'Catalan'],
['ceb', 'Cebuano'],
['ny', 'Chichewa'],
['zh', 'Chinese'],
['hr', 'Croatian'],
['cs', 'Czech'],
['da', 'Danish'],
['nl', 'Dutch'],
['et', 'Estonian'],
['tl', 'Filipino'],
['fi', 'Finnish'],
['fr', 'French'],
['gl', 'Galician'],
['ka', 'Georgian'],
['de', 'German'],
['el', 'Greek'],
['gu', 'Gujarati'],
['ha', 'Hausa'],
['he', 'Hebrew'],
['hi', 'Hindi'],
['hu', 'Hungarian'],
['is', 'Icelandic'],
['ig', 'Igbo'],
['id', 'Indonesian'],
['ga', 'Irish'],
['it', 'Italian'],
['ja', 'Japanese'],
['jv', 'Javanese'],
['kn', 'Kannada'],
['kk', 'Kazakh'],
['ky', 'Kirghiz'],
['ko', 'Korean'],
['lv', 'Latvian'],
['ln', 'Lingala'],
['lt', 'Lithuanian'],
['lb', 'Luxembourgish'],
['mk', 'Macedonian'],
['ms', 'Malay'],
['ml', 'Malayalam'],
['cmn', 'Mandarin Chinese'],
['mr', 'Marathi'],
['ne', 'Nepali'],
['no', 'Norwegian'],
['ps', 'Pashto'],
['fa', 'Persian'],
['pl', 'Polish'],
['pt', 'Portuguese (Brazil)'],
['pa', 'Punjabi'],
['ro', 'Romanian'],
['ru', 'Russian'],
['sr', 'Serbian'],
['sd', 'Sindhi'],
['sk', 'Slovak'],
['sl', 'Slovenian'],
['so', 'Somali'],
['es', 'Spanish'],
['sw', 'Swahili'],
['sv', 'Swedish'],
['ta', 'Tamil'],
['te', 'Telugu'],
['th', 'Thai'],
['tr', 'Turkish'],
['uk', 'Ukrainian'],
['ur', 'Urdu'],
['vi', 'Vietnamese'],
['cy', 'Welsh'],
] as const
export const speechLanguageFlagCodes: Record<string, string> = {
en: 'us',
af: 'za',
ar: 'ae',
hy: 'am',
as: 'in',
az: 'az',
be: 'by',
bn: 'bd',
bs: 'ba',
bg: 'bg',
ca: 'es',
ceb: 'ph',
ny: 'mw',
zh: 'cn',
hr: 'hr',
cs: 'cz',
da: 'dk',
nl: 'nl',
et: 'ee',
tl: 'ph',
fi: 'fi',
fr: 'fr',
gl: 'es',
ka: 'ge',
de: 'de',
el: 'gr',
gu: 'in',
ha: 'ng',
he: 'il',
hi: 'in',
hu: 'hu',
is: 'is',
ig: 'ng',
id: 'id',
ga: 'ie',
it: 'it',
ja: 'jp',
jv: 'id',
kn: 'in',
kk: 'kz',
ky: 'kg',
ko: 'kr',
lv: 'lv',
ln: 'cd',
lt: 'lt',
lb: 'lu',
mk: 'mk',
ms: 'my',
ml: 'in',
cmn: 'cn',
mr: 'in',
ne: 'np',
no: 'no',
ps: 'af',
fa: 'ir',
pl: 'pl',
pt: 'br',
pa: 'in',
ro: 'ro',
ru: 'ru',
sr: 'rs',
sd: 'pk',
sk: 'sk',
sl: 'si',
so: 'so',
es: 'es',
sw: 'tz',
sv: 'se',
ta: 'in',
te: 'in',
th: 'th',
tr: 'tr',
uk: 'ua',
ur: 'pk',
vi: 'vn',
cy: 'gb',
}
export interface SpeechLanguageItem {
id: string
label: string
image: string
}
export const speechLanguageItems: SpeechLanguageItem[] = speechLanguages.map(([id, label]) => ({
id,
label,
image: `https://cdn.spaceui.one/common/flags/circle/${speechLanguageFlagCodes[id]}.svg`,
}))
src/registry/blocks/eleven-labs/eleven-labs-1/data/index.ts
export function getImage(name: string) {
return `https://avatars.spaceui.one/v1?name=${encodeURIComponent(name)}&variant=lumina&size=2000&format=svg`
}
export const productFamilies = [
{
id: 'creative',
label: 'ElevenCreative',
shortLabel: 'Creative',
icon: getImage('elevencreative'),
},
{
id: 'agents',
label: 'ElevenAgents',
shortLabel: 'Agents',
icon: getImage('elevenagents'),
},
{
id: 'api',
label: 'ElevenAPI',
shortLabel: 'API',
icon: getImage('elevenapi'),
},
] as const
export const products = [
{
id: 'voice-generator',
title: 'AI Voice Generator',
},
{
id: 'text-to-speech',
title: 'Text to Speech',
},
{
id: 'music',
title: 'Music',
},
{
id: 'speech-to-text',
title: 'Speech to Text',
},
{
id: 'voice-cloning',
title: 'Voice Cloning',
},
{
id: 'dubbing',
title: 'Dubbing',
},
] as const
export const agentProducts = [
{ id: 'voice', title: 'Voice' },
{ id: 'chat', title: 'Chat' },
] as const
export const apiProducts = [
{ id: 'text-to-speech', title: 'Text to Speech' },
{ id: 'speech-to-text', title: 'Speech to Text' },
{ id: 'music', title: 'Music' },
{ id: 'sound-effects', title: 'Sound Effects' },
] as const
export const voices = [
{
name: 'Nassim - Corporate Narrator',
category: 'Educational',
description: 'Clear, authoritative voices for tutorials and e-learning.',
image: getImage('Nassim'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/e2f30b97a1054e3da83a771cef30028b/voices/repzAAjoKlgcT2oOAIWt/5YNZnGKBIasQeRAM2nc3.mp3',
},
{
name: 'Christopher - Smooth, Deep and Engaging',
category: 'Advertisement',
description: 'Persuasive voices that drive action and brand recall.',
image: getImage('Christopher'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/2dc1073fcb2d4590894250b5466ae19c/voices/SSfU0eLfP3qeuR4j2bwD/FK8rhyz053kOAdg7BQ0B.mp3',
},
{
name: 'Jessica Anne Bogart - Eloquent Villain',
category: 'Characters',
description: 'Playful and engaging voices for cartoons or video games.',
image: getImage('Jessica Anne Bogart'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/user/yA8yDNUx4dZ4gwL9ztbTpUEIyR12/voices/flHkNRp1BlvT73UL6gyz/Wi2FL0vNQp7bJAeSrJaz.mp3',
},
{
name: 'James - Husky Storyteller',
category: 'Narration',
description: 'Expressive voices that bring audiobooks and podcasts to life.',
image: getImage('James'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/48ab3aae468d4e9baded4b1693820088/voices/EkK5I93UQWFDigLMpZcX/xvjT3EK4vD3zlwfawHeV.mp3',
},
{
name: 'Mark - Natural Conversations',
category: 'Conversational',
description: 'Natural voices perfect for informal scenarios.',
image: getImage('Mark'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/f94e260200764678babc807b935bfb0b/voices/UgBBYS2sOqTuMpoF3BR0/0Oc7jiXwWN9kRTXfQsmw.mp3',
},
{
name: 'Hope - Social Media',
category: 'Social Media',
description: 'Trendy, attention-grabbing voices for short-form content.',
image: getImage('Hope'),
preview:
'https://storage.googleapis.com/eleven-public-prod/custom/voices/tnSpp4vdxKPjI9w0GnoV/LiIyxRT1qFJ1QJPr8sWl.mp3',
},
] as const
export const speechVoices = [
{
id: 'NOpBlnGInO9m6vDvFkFC',
label: 'Spuds Oxley',
description: 'Old Storyteller',
image: getImage('Spuds Oxley'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/user/Bi4YhYxPTDRSUfiEpED4qyJ0biq2/voices/NOpBlnGInO9m6vDvFkFC/M4xySW4rr1SbAKKwMAtI.mp3',
},
{
id: 'EkK5I93UQWFDigLMpZcX',
label: 'James',
description: 'Husky Storyteller',
image: getImage('James'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/48ab3aae468d4e9baded4b1693820088/voices/EkK5I93UQWFDigLMpZcX/xvjT3EK4vD3zlwfawHeV.mp3',
},
{
id: '56AoDkrOh6qfVPDXZ7Pt',
label: 'Cassidy',
description: 'Crisp Podcaster',
image: getImage('Cassidy'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/1da06ea679a54975ad96a2221fe6530d/voices/56AoDkrOh6qfVPDXZ7Pt/oEgVi6mikkKcpVcTFfj5.mp3',
},
{
id: 'tnSpp4vdxKPjI9w0GnoV',
label: 'Hope',
description: 'Social Media',
image: getImage('Hope'),
preview:
'https://storage.googleapis.com/eleven-public-prod/custom/voices/tnSpp4vdxKPjI9w0GnoV/LiIyxRT1qFJ1QJPr8sWl.mp3',
},
{
id: 'uju3wxzG5OhpWcoi3SMy',
label: 'Michael C. Vincent',
description: 'Suspenseful Storyteller',
image: getImage('Michael C. Vincent'),
preview:
'https://storage.googleapis.com/eleven-public-prod/custom/voices/uju3wxzG5OhpWcoi3SMy/ixYUu11jiy8LIVZidnvA.mp3',
},
{
id: 'G17SuINrv2H9FC6nvetn',
label: 'Christopher',
description: 'British Storyteller',
image: getImage('Christopher'),
preview:
'https://storage.googleapis.com/eleven-public-prod/custom/voices/G17SuINrv2H9FC6nvetn/ofEmJDtVvrmeL3OEs0ig.mp3',
},
{
id: 'IRHApOXLvnW57QJPQH2P',
label: 'Adam',
description: 'Dark Storyteller',
image: getImage('Adam'),
preview:
'https://api.us.elevenlabs.io/v1/voices/IRHApOXLvnW57QJPQH2P/previews/audio?payload=eyJ2b2ljZV9zb3VyY2UiOiJjdXN0b20iLCJ1c2VyX2lkIjoiNEdXZzBVd1FLWGJpVkZPMFRybDRqTURTVGI4MiIsImZpbGVuYW1lIjoiNzc1ODgyMmYtYzk4Yy00ZGI5LWFiMGEtMTg2YjZhMTNiZTFhLm1wMyIsInRpbWVzdGFtcCI6MTc4MzQ4NjgwMDAwMDAwMH0%3D',
},
{
id: 'EiNlNiXeDU1pqqOPrYMO',
label: 'John Doe',
description: 'Gravely Storyteller',
image: getImage('John Doe'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/user/8RYUsFHsalUXjwVG0LjhNwH1j022/voices/EiNlNiXeDU1pqqOPrYMO/DyICRR04KQnCeB7Y9B7K.mp3',
},
{
id: '1SM7GgM6IMuvQlz2BwM3',
label: 'Mark',
description: 'Friendly Companion',
image: getImage('Mark'),
preview:
'https://api.us.elevenlabs.io/v1/voices/1SM7GgM6IMuvQlz2BwM3/previews/audio?payload=eyJ2b2ljZV9zb3VyY2UiOiJjdXN0b20iLCJ3b3Jrc3BhY2VfaWQiOiJmOTRlMjYwMjAwNzY0Njc4YmFiYzgwN2I5MzViZmIwYiIsImZpbGVuYW1lIjoieTk0RzFyR2l4YXFMMkZ2UDNUdGUubXAzIiwidGltZXN0YW1wIjoxNzgzNDg2ODAwMDAwMDAwfQ%3D%3D',
},
{
id: 'ZthjuvLPty3kTMaNKVKb',
label: 'Peter',
description: 'Confident Storyteller',
image: getImage('Peter'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/user/T05GPpk2fvSBb625DXd4BJHZtu22/voices/ZthjuvLPty3kTMaNKVKb/2nBO0beUcX6fsrvPZbcE.mp3',
},
{
id: 'Z3R5wn05IrDiVCyEkUrK',
label: 'Arabella',
description: 'Romance Storyteller',
image: getImage('Arabella'),
preview:
'https://api.us.elevenlabs.io/v1/voices/Z3R5wn05IrDiVCyEkUrK/previews/audio?payload=eyJ2b2ljZV9zb3VyY2UiOiJjdXN0b20iLCJ3b3Jrc3BhY2VfaWQiOiI2N2FkYWRiN2QyYTk0ZjZlYWQ2NGU5NWY0NWJlMjI1NCIsImZpbGVuYW1lIjoiQ0JZS2FmbzVvbkllNTIzNHJBR1MubXAzIiwidGltZXN0YW1wIjoxNzgzNDg2ODAwMDAwMDAwfQ%3D%3D',
},
{
id: 'lxYfHSkYm1EzQzGhdbfc',
label: 'Jessica Anne Bogart',
description: 'Romance Storyteller',
image: getImage('Jessica Anne Bogart'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/fb1bd18708014d8d80e770df5ea11bb7/voices/lxYfHSkYm1EzQzGhdbfc/JZDpsUoP3T2VAnEEmj4l.mp3',
},
{
id: 'e5WNhrdI30aXpS2RSGm1',
label: 'Ian Cartwell',
description: 'Mystery Storyteller',
image: getImage('Ian Cartwell'),
preview:
'https://api.us.elevenlabs.io/v1/voices/e5WNhrdI30aXpS2RSGm1/previews/audio?payload=eyJ2b2ljZV9zb3VyY2UiOiJjdXN0b20iLCJ1c2VyX2lkIjoiVDdiaHF4THlFTmdSYVNnZm5xaFBhNnZQU0lEMiIsImZpbGVuYW1lIjoiaXJaNlpUYkdWQTVXalZBd25aY0oubXAzIiwidGltZXN0YW1wIjoxNzgzNDg2ODAwMDAwMDAwfQ%3D%3D',
},
{
id: 'yl2ZDV1MzN4HbQJbMihG',
label: 'Alex',
description: 'Social Media',
image: getImage('Alex'),
preview:
'https://storage.googleapis.com/eleven-public-prod/custom/voices/yl2ZDV1MzN4HbQJbMihG/TxJIOexqYqCv1Dzexs6Y.mp3',
},
{
id: 'kqVT88a5QfII1HNAEPTJ',
label: 'Declan Sage',
description: 'Authoritative Storyteller',
image: getImage('Declan Sage'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/5d6438deeb7d443ca0a6fc6309d5bb8a/voices/kqVT88a5QfII1HNAEPTJ/Hex3MZnpQ3dCbdgTkKyr.mp3',
},
{
id: 'NNl6r8mD7vthiJatiJt1',
label: 'Bradford',
description: 'Articulated Storyteller',
image: getImage('Bradford'),
preview:
'https://api.us.elevenlabs.io/v1/voices/NNl6r8mD7vthiJatiJt1/previews/audio?payload=eyJ2b2ljZV9zb3VyY2UiOiJjdXN0b20iLCJ1c2VyX2lkIjoiQ0VaWHhmZENmRWZ5WEQ3eVVOS0lQY3VDS2FCMyIsImZpbGVuYW1lIjoiNmFkY2E0ZTItMGJjMS00ZWNlLWFjMDItZmYwYmNlYWM5YzM2Lm1wMyIsInRpbWVzdGFtcCI6MTc4MzQ4NjgwMDAwMDAwMH0%3D',
},
{
id: 'XjLkpWUlnhS8i7gGz3lZ',
label: 'David Castlemore',
description: 'Newsreader',
image: getImage('David Castlemore'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/user/T7bhqxLyENgRaSgfnqhPa6vPSID2/voices/XjLkpWUlnhS8i7gGz3lZ/2foqwf4lPNBPWW27H6du.mp3',
},
{
id: 'j9jfwdrw7BRfcR43Qohk',
label: 'Frederick Surrey',
description: 'Science Storyteller',
image: getImage('Frederick Surrey'),
preview:
'https://storage.googleapis.com/eleven-public-prod/custom/voices/j9jfwdrw7BRfcR43Qohk/Vyj86dr4NJ1Tr82nEPdw.mp3',
},
{
id: 'MFZUKuGQUsGJPQjTS4wC',
label: 'Jon',
description: 'Support Agent',
image: getImage('Jon'),
preview:
'https://storage.googleapis.com/eleven-public-prod/database/workspace/7bdd69d9e581481a8ea5216493271f81/voices/MFZUKuGQUsGJPQjTS4wC/xEoCh0QqT3VPBukUnnNT.mp3',
},
] as const
export const speechInitialText =
'In the ancient land of Eldoria, where skies shimmered and forests, whispered secrets to the wind, lived a dragon named Zephyros. [sarcastically] Not the “burn it all down” kind... [giggles] but he was gentle, wise, with eyes like old stars. [whispers] Even the birds fell silent when he passed.'
export const initialAgentMessages = [
{
id: 1,
from: 'agent' as const,
text: "Hi there! I'm a support technician from LaptopCare Solutions. How can I help you today?",
},
{ id: 2, from: 'user' as const, text: 'hi' },
{
id: 3,
from: 'agent' as const,
text: "Hi! My name is River, and I'm here to help you with your computer issue today. To start, could you please tell me a little bit about what's going on?",
},
]
export const demoAgents = [
{
id: 'support',
label: 'Support Agent',
image: getImage('Support Agent'),
},
{
id: 'food',
label: 'Food Order Agent',
image: getImage('Food Order Agent'),
},
{
id: 'math',
label: 'Math Teacher',
image: getImage('Math Teacher'),
},
] as const
src/registry/blocks/eleven-labs/eleven-labs-1/sections/creative-platform/editor-demo.tsx
'use client'
import * as React from 'react'
import { IconSparkles } from '@tabler/icons-react'
import { SilkGradient } from '@/components/shader/silk-gradient'
function createTimelineThumbnail(color1 = '#ce4f31', color2 = '#74afe0', color3 = '#324298'): string {
if (typeof document === 'undefined') return ''
try {
const canvas = document.createElement('canvas')
canvas.width = 96
canvas.height = 64
const ctx = canvas.getContext('2d')
if (!ctx) return ''
const grad = ctx.createLinearGradient(0, 64, 96, 0)
grad.addColorStop(0, color3)
grad.addColorStop(0.35, color1)
grad.addColorStop(0.65, color2)
grad.addColorStop(1, color3)
ctx.fillStyle = grad
ctx.fillRect(0, 0, 96, 64)
const imgData = ctx.getImageData(0, 0, 96, 64)
const data = imgData.data
for (let i = 0; i < data.length; i += 4) {
const noise = (Math.random() - 0.5) * 16
data[i] = Math.min(255, Math.max(0, data[i] + noise))
data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + noise))
data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + noise))
}
ctx.putImageData(imgData, 0, 0)
return canvas.toDataURL('image/webp', 0.9)
} catch {
return ''
}
}
export function EditorDemo() {
const [timelineUrl, setTimelineUrl] = React.useState<string>('')
React.useEffect(() => {
setTimelineUrl(createTimelineThumbnail('#ce4f31', '#74afe0', '#324298'))
}, [])
return (
<article className="relative isolate h-full overflow-hidden rounded-[20px] bg-[#f5f3f1]">
<SilkGradient
className="pointer-events-none absolute inset-0 -z-20 size-full"
color1="#d36f5b"
color2="#b5cdec"
color3="#bbbfd3"
animate
speed={0.85}
grain
/>
<div
className="absolute inset-0 -z-10"
style={{
background:
'linear-gradient(to top, rgba(0, 0, 0, 0.35) 0%, rgba(0, 0, 0, 0.34554) 8.07%, rgba(0, 0, 0, 0.33299) 15.54%, rgba(0, 0, 0, 0.3136) 22.5%, rgba(0, 0, 0, 0.28861) 29.04%, rgba(0, 0, 0, 0.25926) 35.26%, rgba(0, 0, 0, 0.2268) 41.25%, rgba(0, 0, 0, 0.19247) 47.1%, rgba(0, 0, 0, 0.15753) 52.9%, rgba(0, 0, 0, 0.1232) 58.75%, rgba(0, 0, 0, 0.09074) 64.74%, rgba(0, 0, 0, 0.06139) 70.96%, rgba(0, 0, 0, 0.0364) 77.5%, rgba(0, 0, 0, 0.01701) 84.46%, rgba(0, 0, 0, 0.00446) 91.93%, rgba(0, 0, 0, 0) 100%)',
}}
/>
<div className="relative flex h-full flex-col px-5 pb-6 sm:px-7 sm:pb-8">
<div className="relative z-20 mr-[-20px] flex h-[474px] pb-12 sm:mr-[-28px]">
<div className="flex w-full flex-col rounded-bl-[20px] bg-[#f5f3f1]">
<div className="flex w-full flex-auto">
<div className="w-[70%] p-8">
<p className="line-clamp-6 text-[13px] leading-[18px] tracking-[.01em] text-[#59544f]">
Amidst the outer atmosphere of the planet Aurora, the sky shimmered with fractured light, as though
the planet's veil were made of stained glass suspended in space.
</p>
<p className="mt-4 line-clamp-4 text-[13px] leading-[18px] tracking-[.01em] text-[#a9a49e]">
Sensors pulsed with irregular patterns, the kind no algorithm could quite reconcile.
</p>
</div>
<div className="flex w-[30%] border-l border-[#dedbd8] py-10 pl-10">
<div className="relative h-full w-full overflow-hidden rounded-l-lg opacity-80">
<SilkGradient
className="size-full object-cover object-left"
color1="#ce4f31"
color2="#74afe0"
color3="#324298"
animate
speed={0.85}
grain
/>
</div>
</div>
</div>
<div className="w-full border-t border-[#dedbd8] p-4">
<div className="w-fit rounded-[11.5px] p-[3.5px] ring-[1.5px] ring-black ring-inset">
<div
className="relative h-8 w-60 overflow-hidden rounded-lg bg-[#324298]"
style={{
backgroundImage: timelineUrl ? `url(${timelineUrl})` : undefined,
backgroundSize: '48px 32px',
backgroundRepeat: 'repeat-x',
}}
>
<div className="pointer-events-none absolute inset-0 grid grid-cols-5 divide-x divide-white/20">
<div />
<div />
<div />
<div />
<div />
</div>
</div>
</div>
<div className="mt-3 flex w-full gap-x-2">
<div className="flex h-9 min-w-0 w-80 shrink items-center rounded-lg bg-white px-3 shadow-[0_0_1px_rgb(0_0_0_/_0.4),0_2px_4px_rgb(0_0_0_/_0.04)]">
<p className="truncate text-[13px] leading-[18px] text-[#777169]">
Amidst the outer atmosphere of the planet Aurora, the sky shimmered with fractured light, as though
the planet's veil were made of stained glass suspended in space.
</p>
</div>
<div className="flex h-9 w-14 flex-none items-center justify-center rounded-lg bg-white text-[#777169] shadow-[0_0_1px_rgb(0_0_0_/_0.4),0_2px_4px_rgb(0_0_0_/_0.04)]">
<IconSparkles className="size-4" aria-hidden="true" />
</div>
</div>
</div>
</div>
</div>
<div className="order-last mt-auto flex shrink-0 flex-col">
<h3 className="text-[15px] leading-[22px] tracking-[.01em] text-white/80">All-in-one AI editor</h3>
<p className="mt-[18px] text-[15px] leading-[22px] tracking-[.01em] text-pretty text-white">
Create podcasts, audiobooks and voiceovers in an editor built on all of ElevenLabs’ audio research.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-[20px] ring-[.5px] ring-black/[.075] ring-inset" />
</article>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/creative-platform/index.tsx
import { IconDisc, IconSparkles, IconVideo, IconWaveSine } from '@tabler/icons-react'
import { cn } from '@/lib/utils'
import { SectionDivider, focus } from '../../components/shared'
import { EditorDemo } from './editor-demo'
import { SpeechDemo } from './speech-demo'
export function FeatureIcon({ type }: { type: 'music' | 'sfx' | 'voices' | 'video' }) {
if (type === 'music') {
return <IconDisc className="size-6" strokeWidth={1.5} aria-hidden="true" />
}
if (type === 'sfx') {
return <IconSparkles className="size-6" strokeWidth={1.5} aria-hidden="true" />
}
if (type === 'voices') {
return <IconWaveSine className="size-6" strokeWidth={1.5} aria-hidden="true" />
}
return <IconVideo className="size-6" strokeWidth={1.5} aria-hidden="true" />
}
export function SmallFeatureCard({
title,
description,
type,
}: {
title: string
description: string
type: 'music' | 'sfx' | 'voices' | 'video'
}) {
return (
<article className="relative isolate flex h-full flex-col overflow-hidden rounded-[20px] bg-[#f5f3f1] px-5 pt-5 pb-6 sm:px-7 sm:pt-7 sm:pb-8">
<div className="mb-16 flex size-10 items-center justify-center rounded-[10px] ring-[.5px] ring-black/10 ring-inset">
<FeatureIcon type={type} />
</div>
<div className="order-last mt-auto flex shrink-0 flex-col">
<h3 className="text-[15px] leading-[22px] tracking-[.01em] text-[#777169]">
<span className="inline-flex items-center gap-1.5 font-medium text-black">{title}</span>
</h3>
<p className="mt-[18px] text-[15px] leading-[22px] tracking-[.01em] text-pretty text-black">{description}</p>
</div>
<div className="pointer-events-none absolute inset-0 rounded-[20px] ring-[.5px] ring-black/[.075] ring-inset" />
</article>
)
}
export function CreativePlatformSection() {
return (
<section aria-labelledby="creative-platform-title">
<SectionDivider />
<div className="mx-auto max-w-[1304px] border-x border-black/[.075] px-4 pt-[120px] pb-24 md:px-12 md:pt-40 md:pb-40">
<p className="mb-7 text-[15px] leading-[22px] font-medium tracking-[.01em] text-[#777169] before:table before:mb-[-5.5px] before:content-[''] after:table after:mt-[-6px] after:content-['']">
ElevenCreative
</p>
<div className="flex flex-col lg:grid lg:grid-cols-12 lg:gap-x-12">
<div className="contents lg:col-span-6 lg:block">
<h2
id="creative-platform-title"
className="max-w-lg [font-family:var(--font-waldenburg)] text-[30px] leading-9 font-light text-balance before:table before:mb-[-7px] before:content-[''] after:table after:mt-[-7.5px] after:content-[''] sm:text-[36px] sm:leading-[42px] sm:before:mb-[-7.5px] sm:after:mt-[-8.5px] lg:max-w-none"
>
Create, edit, and localize in one AI platform
</h2>
<div className="order-last mt-8 lg:order-none">
<button
type="button"
className={cn(
'inline-flex h-11 w-fit items-center justify-center rounded-full bg-black px-5 text-[16px] leading-6 whitespace-nowrap text-white transition hover:bg-[#393735] active:scale-[.98]',
focus,
)}
>
Learn more
</button>
</div>
</div>
<div className="mt-8 flex max-w-2xl flex-col lg:col-span-6 lg:mt-0 lg:max-w-none">
<h3 className="mt-auto text-[16px] leading-6 font-normal tracking-[.01em] text-balance before:table before:mb-[-6px] before:content-[''] after:table after:mt-[-6.5px] after:content-[''] lg:text-pretty">
Create ultra-realistic speech, turn ideas into videos, compose music in any genre, or design immersive
sound effects. Craft your next film, ad, campaign, social content, audiobook, or podcast with our creative
platform.
</h3>
<div className="h-0 max-h-[76px] flex-auto" />
</div>
</div>
</div>
<div className="mx-auto max-w-[1304px] border-x border-black/[.075] px-2 md:px-4">
<div className="grid grid-cols-12 gap-4">
<div className="col-span-full h-full lg:col-span-6">
<EditorDemo />
</div>
<div className="col-span-full h-full lg:col-span-6">
<SpeechDemo />
</div>
<div className="col-span-full h-full sm:col-span-6 lg:col-span-3">
<SmallFeatureCard
type="music"
title="Music"
description="Generate studio-quality tracks instantly, any genre, any style, vocals or instrumental."
/>
</div>
<div className="col-span-full h-full sm:col-span-6 lg:col-span-3">
<SmallFeatureCard
type="sfx"
title="SFX"
description="Create custom sound effects, soundscapes and ambient audio or search the SFX library."
/>
</div>
<div className="col-span-full h-full sm:col-span-6 lg:col-span-3">
<SmallFeatureCard
type="voices"
title="Voices"
description="Clone your voice, design one from a prompt, or explore 10,000+ voices from the library."
/>
</div>
<div className="col-span-full h-full sm:col-span-6 lg:col-span-3">
<SmallFeatureCard
type="video"
title="Image & Video"
description="Create or edit images and turn ideas into videos with leading models like Veo, Wan, Kling and Seedance."
/>
</div>
</div>
</div>
</section>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/creative-platform/speech-demo.tsx
'use client'
import { useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import { focus, SpeechLoadingSpinner } from '../../components/shared'
import { speechInitialText, speechVoices } from '../../data'
import { speechLanguageItems } from '../../data/flags'
import { SpeechSelect } from '../../components/speech-select'
export function SpeechDemo() {
const [speechText, setSpeechText] = useState(speechInitialText)
const [language, setLanguage] = useState('en')
const [voiceId, setVoiceId] = useState<string>(speechVoices[0].id)
const [playing, setPlaying] = useState(false)
const [loading, setLoading] = useState(false)
const speechAudioRef = useRef<HTMLAudioElement>(null)
const selectedVoice = speechVoices.find((voice) => voice.id === voiceId) ?? speechVoices[0]
const toggleSpeech = () => {
const audio = speechAudioRef.current
if (!audio || loading) return
if (audio.paused) {
setLoading(true)
void audio
.play()
.then(() => {
setLoading(false)
setPlaying(true)
})
.catch(() => {
setLoading(false)
setPlaying(false)
})
} else {
audio.pause()
setLoading(false)
setPlaying(false)
}
}
const highlightedText = speechText.split(/(\[[^\]]+\])/g)
return (
<article className="relative isolate h-full overflow-hidden rounded-[20px] bg-[#f5f3f1]">
<div className="relative flex h-full flex-col px-5 pb-6 sm:px-7 sm:pb-8">
<div className="relative z-20 h-[474px] overflow-visible">
<div className="flex h-full items-center">
<div className="relative isolate -mt-9 mx-auto w-full max-w-[592px] rounded-3xl bg-white ring-[.5px] ring-[#dedbd8] ring-inset">
<div className="relative flex flex-col">
<div className="relative flex h-40 flex-auto flex-col overflow-y-auto [scrollbar-width:none]">
<div className="relative flex flex-auto flex-col">
<div className="relative flex-auto">
<div className="px-5 pt-4 text-[16px] leading-6 text-black whitespace-pre-wrap">
{highlightedText.map((part, index) =>
part.startsWith('[') ? (
<span key={`${part}-${index}`} className="text-[#a9a49e]">
{part}
</span>
) : (
part
),
)}
</div>
<textarea
value={speechText}
onChange={(event) => setSpeechText(event.target.value)}
aria-label="Enter your text here, ElevenLabs AI Voice Generator will read it for you"
maxLength={1000}
spellCheck={false}
className={cn(
focus,
'absolute inset-0 size-full resize-none overflow-hidden bg-transparent px-5 pt-4 text-[16px] leading-6 text-transparent caret-black outline-none',
)}
/>
</div>
</div>
<div className="sticky bottom-0 h-6 flex-none bg-gradient-to-t from-white" />
</div>
<div className="relative z-10 flex items-center px-3 pb-3">
<SpeechSelect
ariaLabel="Language"
value={language}
items={speechLanguageItems}
onChange={setLanguage}
menuWidth={214}
hideTriggerLabelOnMobile
/>
<SpeechSelect
ariaLabel="Voice"
value={voiceId}
items={speechVoices}
onChange={(nextVoiceId) => {
speechAudioRef.current?.pause()
setLoading(false)
setPlaying(false)
setVoiceId(nextVoiceId)
}}
menuWidth={372}
hideTriggerImageOnMobile
/>
<button
type="button"
aria-label={playing ? 'Pause' : 'Play'}
disabled={loading}
onClick={toggleSpeech}
className={cn(
'ml-auto flex flex-none items-center justify-center rounded-full bg-black text-[15px] leading-none text-white transition-colors hover:bg-[#59544f] disabled:cursor-default',
loading ? 'size-9 px-0' : 'h-9 px-3.5',
focus,
)}
>
{loading ? <SpeechLoadingSpinner /> : playing ? 'Pause' : 'Play'}
</button>
<audio
ref={speechAudioRef}
src={selectedVoice.preview}
preload="none"
className="hidden"
onPlaying={() => {
setLoading(false)
setPlaying(true)
}}
onWaiting={() => setLoading(true)}
onPause={() => {
setLoading(false)
setPlaying(false)
}}
onEnded={() => {
setLoading(false)
setPlaying(false)
}}
/>
</div>
</div>
</div>
</div>
</div>
<div className="order-last mt-auto flex shrink-0 flex-col">
<h3 className="text-[15px] leading-[22px] tracking-[.01em] font-normal text-[#777169]">
<span className="inline-flex items-center gap-1.5">Ultra-realistic speech</span>
</h3>
<p className="mt-[18px] text-[15px] leading-[22px] tracking-[.01em] text-pretty text-black">
Create controllable, expressive speech layered across 70+ languages.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-[20px] ring-[.5px] ring-black/[.075] ring-inset" />
</article>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/product-tabs/agents/index.tsx
'use client'
import type { FormEvent } from 'react'
import { useCallback, useEffect, useId, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { cn } from '@/lib/utils'
import { IconCheck, IconMicrophoneOff, IconPhoneFilled } from '@tabler/icons-react'
import { OrbSmooth } from '@/components/orb/smooth/index'
import { demoAgents, initialAgentMessages } from '../../../data'
import { focus } from '../../../components/shared'
import { SelectChevron } from '../../../components/speech-select'
export function PhoneIcon() {
return <IconPhoneFilled aria-hidden="true" className="size-5" />
}
export function MicOffIcon() {
return <IconMicrophoneOff aria-hidden="true" strokeWidth={1.7} className="size-5" />
}
export function AgentsDemo({ mode }: { mode: 'voice' | 'chat' }) {
const [calling, setCalling] = useState(false)
const [agentMenuOpen, setAgentMenuOpen] = useState(false)
const [selectedAgentId, setSelectedAgentId] = useState('support')
const [agentMenuPosition, setAgentMenuPosition] = useState({
left: 0,
top: 0,
})
const [messages, setMessages] = useState(initialAgentMessages)
const [message, setMessage] = useState('')
const agentTriggerRef = useRef<HTMLButtonElement>(null)
const agentMenuRef = useRef<HTMLDivElement>(null)
const agentListboxId = useId()
const selectedAgent = demoAgents.find((agent) => agent.id === selectedAgentId) ?? demoAgents[0]
const updateAgentMenuPosition = useCallback(() => {
const trigger = agentTriggerRef.current
if (!trigger) return
const rect = trigger.getBoundingClientRect()
setAgentMenuPosition({
left: Math.max(8, rect.left - 4),
top: rect.bottom + 8,
})
}, [])
useEffect(() => {
if (!agentMenuOpen) return
updateAgentMenuPosition()
const closeFromOutside = (event: PointerEvent) => {
const target = event.target as Node
if (!agentTriggerRef.current?.contains(target) && !agentMenuRef.current?.contains(target)) {
setAgentMenuOpen(false)
}
}
const closeFromEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setAgentMenuOpen(false)
agentTriggerRef.current?.focus()
}
}
document.addEventListener('pointerdown', closeFromOutside)
document.addEventListener('keydown', closeFromEscape)
window.addEventListener('resize', updateAgentMenuPosition)
window.addEventListener('scroll', updateAgentMenuPosition, true)
return () => {
document.removeEventListener('pointerdown', closeFromOutside)
document.removeEventListener('keydown', closeFromEscape)
window.removeEventListener('resize', updateAgentMenuPosition)
window.removeEventListener('scroll', updateAgentMenuPosition, true)
}
}, [agentMenuOpen, updateAgentMenuPosition])
const appendMessage = () => {
const nextMessage = message.trim()
if (!nextMessage) return
setMessages((current) => [...current, { id: Date.now(), from: 'user', text: nextMessage }])
setMessage('')
}
const submitMessage = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
appendMessage()
}
return (
<>
<div className="absolute inset-0 z-[5] flex items-center justify-center">
<div className="relative flex h-[332px] w-72 max-w-full flex-col">
<div className="pointer-events-none absolute inset-0 z-20">
<div
className={cn(
'relative mx-auto aspect-square w-64 max-w-full origin-top transition-transform duration-[400ms] ease-in-out',
mode === 'chat' ? '-translate-x-4 rotate-90' : 'translate-x-0 rotate-0',
)}
>
<div
className={cn(
'absolute inset-0 origin-center overflow-hidden rounded-full transition-transform duration-[400ms] ease-in-out',
mode === 'chat'
? 'translate-x-5 -translate-y-5 -rotate-90 scale-[0.15625]'
: 'translate-x-0 translate-y-0 rotate-0 scale-100',
)}
>
<img
src={selectedAgent.image}
alt=""
className={cn(
'size-full object-cover transition-transform duration-700',
calling ? 'scale-[1.04]' : 'scale-100',
)}
/>
<div
className={cn(
'absolute -inset-2 transition-all duration-700',
calling ? 'animate-[pulse_1.8s_ease-in-out_infinite] scale-110 opacity-55' : 'scale-100 opacity-0',
)}
>
<img src={selectedAgent.image} alt="" className="size-full object-cover blur-[3px]" />
</div>
<div
aria-hidden="true"
className={cn(
'pointer-events-none absolute inset-0 z-10 overflow-hidden rounded-full transition-opacity duration-500',
calling ? 'opacity-100' : 'opacity-0',
)}
>
<OrbSmooth
size={256}
textureUrl={selectedAgent.image}
audioMode={calling ? 'mic' : 'ambient'}
sphereScale={0.9}
spherePower={1.1}
grainOpacity={0.67}
animated
className="size-full"
/>
</div>
<span className="absolute inset-0 z-20 rounded-full ring-[.5px] ring-black/[.075] ring-inset" />
</div>
</div>
</div>
<div
aria-hidden={mode !== 'chat'}
className={cn(
'relative z-10 aspect-square max-h-64 w-72 max-w-full transition-opacity duration-300',
mode === 'chat' ? 'opacity-100 delay-150' : 'pointer-events-none opacity-0',
)}
>
<div className="absolute inset-x-0 top-0 -bottom-8 flex flex-col pt-16 pb-8 [mask:linear-gradient(transparent,white_4rem,white_calc(100%-2rem),transparent)]">
<div className="flex min-h-full items-end">
<div className="flex max-h-[192px] flex-auto flex-col gap-4 overflow-y-auto [scrollbar-width:none]">
{messages.map((item) => (
<p
key={item.id}
className={cn(
'text-[15px] leading-5 text-black',
item.from === 'user' &&
'mr-1 ml-auto mt-1 w-fit max-w-[calc(100%-4rem)] rounded-[20px] bg-white px-4 py-2.5 shadow-[0_0_0_.897px_rgb(0_0_0_/_0.07),0_2px_4px_rgb(0_0_0_/_0.08)]',
)}
>
{item.text}
</p>
))}
</div>
</div>
</div>
</div>
<button
type="button"
aria-label={calling ? 'End call' : 'Start call'}
aria-pressed={calling}
onClick={() => setCalling((current) => !current)}
className={cn(
'group absolute top-0 left-0 z-30 flex h-64 w-72 items-center justify-center outline-none transition-opacity duration-200',
focus,
mode === 'voice' ? 'opacity-100 delay-200' : 'pointer-events-none opacity-0',
)}
>
<span className="absolute bottom-0 left-1/2 flex size-16 -translate-x-1/2 translate-y-8 items-center justify-center rounded-full bg-[#f5f3f1] p-2">
<span className="flex size-12 items-center justify-center rounded-full bg-black text-white shadow-[0_0_1px_rgb(0_0_0_/_0.4),0_1px_1px_rgb(0_0_0_/_0.04),0_2px_4px_rgb(0_0_0_/_0.04)] transition group-hover:bg-[#59544f]">
{calling ? <MicOffIcon /> : <PhoneIcon />}
</span>
</span>
</button>
<form
onSubmit={submitMessage}
aria-hidden={mode !== 'chat'}
className={cn(
'relative mt-8 h-11 w-full transition-opacity duration-300',
mode === 'chat' ? 'opacity-100 delay-150' : 'pointer-events-none opacity-0',
)}
>
<input
value={message}
onChange={(event) => setMessage(event.target.value)}
onKeyDown={(event) => {
if (event.key !== 'Enter' || event.shiftKey) return
event.preventDefault()
appendMessage()
}}
placeholder="Type a message…"
aria-label="Type a message"
tabIndex={mode === 'chat' ? 0 : -1}
className={cn(
'h-11 w-full rounded-full bg-white px-[18px] text-[16px] leading-6 text-black outline-none placeholder:text-[#a9a49e] shadow-[0_0_1px_rgb(0_0_0_/_0.4),0_1px_1px_rgb(0_0_0_/_0.04),0_2px_4px_rgb(0_0_0_/_0.04)]',
focus,
)}
/>
</form>
</div>
</div>
<div className="absolute bottom-6 left-8 z-20 hidden md:block">
<button
ref={agentTriggerRef}
type="button"
aria-haspopup="listbox"
aria-expanded={agentMenuOpen}
aria-controls={agentMenuOpen ? agentListboxId : undefined}
onClick={() => {
updateAgentMenuPosition()
setAgentMenuOpen((current) => !current)
}}
className={cn(
'flex h-10 items-center gap-2 rounded-full py-0 pr-3 pl-0.5 text-[15px] leading-5 text-black transition-colors hover:bg-[#eeeceb]',
focus,
)}
>
<span className="relative -ml-[1.5px] flex size-10 flex-none items-center justify-center overflow-hidden rounded-full bg-white ring-1 ring-black/10 shadow-[0_0_0_1px_rgb(0_0_0_/_0.06),0_1px_2px_rgb(0_0_0_/_0.04),0_2px_4px_rgb(0_0_0_/_0.04)]">
<span className="relative size-7 overflow-hidden rounded-full">
<img src={selectedAgent.image} alt="" className="size-full object-cover" />
</span>
</span>
<span className="whitespace-nowrap">{selectedAgent.label}</span>
<SelectChevron />
</button>
</div>
{agentMenuOpen &&
createPortal(
<div
ref={agentMenuRef}
id={agentListboxId}
role="listbox"
aria-label="Choose an agent"
className="fixed z-[200] w-[211px] rounded-2xl bg-white p-1.5 text-[15px] leading-5 text-black shadow-[0_0_1px_rgb(0_0_0_/_0.4),0_4px_4px_rgb(0_0_0_/_0.04)]"
style={{
left: agentMenuPosition.left,
top: agentMenuPosition.top,
}}
>
{demoAgents.map((agent) => {
const selected = agent.id === selectedAgent.id
return (
<button
key={agent.id}
type="button"
role="option"
aria-selected={selected}
onClick={() => {
setSelectedAgentId(agent.id)
setCalling(false)
setAgentMenuOpen(false)
agentTriggerRef.current?.focus()
}}
className={cn(
'flex h-9 w-full items-center gap-2 rounded-xl px-2.5 text-left transition-colors hover:bg-[#f5f3f1]',
focus,
)}
>
<span className="relative size-4 flex-none overflow-hidden rounded-full">
<img src={agent.image} alt="" className="size-full object-cover" />
</span>
<span className="min-w-0 flex-1 truncate">{agent.label}</span>
{selected && (
<IconCheck aria-hidden="true" strokeWidth={1.5} className="size-5 flex-none text-black" />
)}
</button>
)
})}
</div>,
document.body,
)}
</>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/product-tabs/api/text-to-speech.tsx
'use client'
import { useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import { focus, SpeechLoadingSpinner } from '../../../components/shared'
import { SpeechSelect } from '../../../components/speech-select'
import { CodeLanguageSelect, ApiPromptInput, CopyIcon } from '../../../components/code-controls'
import { speechInitialText, speechVoices } from '../../../data'
import { speechLanguageItems } from '../../../data/flags'
export function ApiTextToSpeechDemo() {
const [language, setLanguage] = useState('en')
const [voiceId, setVoiceId] = useState<string>(speechVoices[0].id)
const [codeLanguage, setCodeLanguage] = useState<'ts' | 'python'>('ts')
const [prompt, setPrompt] = useState(speechInitialText)
const [copied, setCopied] = useState(false)
const [playing, setPlaying] = useState(false)
const [loading, setLoading] = useState(false)
const audioRef = useRef<HTMLAudioElement>(null)
const selectedVoice = speechVoices.find((voice) => voice.id === voiceId) ?? speechVoices[0]
const code =
codeLanguage === 'ts'
? `import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
const elevenlabs = new ElevenLabsClient();
const audio = await elevenlabs
.textToSpeech.convert("${voiceId}", {
text: "${prompt}",
modelId: "eleven_v3",
languageCode: "${language}",
});`
: `from elevenlabs.client import ElevenLabs
elevenlabs = ElevenLabs()
audio = elevenlabs.text_to_speech.convert(
voice_id="${voiceId}",
text="${prompt}",
model_id="eleven_v3",
language_code="${language}",
)`
const copyCode = async () => {
try {
await navigator.clipboard.writeText(code)
setCopied(true)
window.setTimeout(() => setCopied(false), 1400)
} catch {
setCopied(false)
}
}
const runCode = () => {
const audio = audioRef.current
if (!audio || loading) return
if (audio.paused) {
setLoading(true)
void audio
.play()
.then(() => {
setLoading(false)
setPlaying(true)
})
.catch(() => {
setLoading(false)
setPlaying(false)
})
} else {
audio.pause()
setLoading(false)
setPlaying(false)
}
}
return (
<div className="absolute top-[68px] left-1/2 z-[5] flex h-80 w-[calc(100%-32px)] max-w-[640px] -translate-x-1/2 flex-col overflow-hidden rounded-3xl bg-white shadow-[inset_0_0_0_.5px_rgb(0_0_0_/_0.075)] md:top-[69px]">
<div className="min-h-0 flex-1 overflow-auto px-5 pt-4 [font-family:var(--font-geist-mono),monospace] text-[12px] leading-[21px] text-black [scrollbar-width:none] sm:text-[13px] sm:leading-[26px]">
{codeLanguage === 'ts' ? (
<>
<p className="mb-[26px]">
<span className="text-[#f41a2f]">import</span> {'{ ElevenLabsClient } '}
<span className="text-[#f41a2f]">from</span>{' '}
<span className="text-[#052f70]">"@elevenlabs/elevenlabs-js"</span>;
</p>
<p>
<span className="text-[#f41a2f]">const</span> <span className="text-[#0a59d2]">elevenlabs</span> = new
ElevenLabsClient();
</p>
<p>
<span className="text-[#f41a2f]">const</span> <span className="text-[#0a59d2]">audio</span> ={' '}
<span className="text-[#f41a2f]">await</span> elevenlabs
</p>
<p className="pl-4">
.textToSpeech.convert("
<SpeechSelect
ariaLabel="Voice"
value={voiceId}
items={speechVoices}
onChange={setVoiceId}
menuWidth={304}
inlineCode
triggerValue={voiceId}
/>
", {'{'}
</p>
<p className="flex min-w-0 items-center pl-8">
<span className="whitespace-pre text-[#0a59d2]">text:</span>
<span className="whitespace-pre"> "</span>
<ApiPromptInput value={prompt} onChange={setPrompt} />
<span className="whitespace-pre">",</span>
</p>
<p className="pl-8">
<span className="text-[#0a59d2]">modelId:</span>{' '}
<span className="text-[#052f70]">"eleven_v3"</span>,
</p>
<p className="pl-8">
<span className="text-[#0a59d2]">languageCode:</span> "
<SpeechSelect
ariaLabel="Language"
value={language}
items={speechLanguageItems}
onChange={setLanguage}
menuWidth={222}
inlineCode
triggerValue={language}
/>
",
</p>
<p className="pl-4">{'});'}</p>
</>
) : (
<>
<p className="mb-[26px]">
<span className="text-[#f41a2f]">from</span> elevenlabs.client{' '}
<span className="text-[#f41a2f]">import</span> ElevenLabs
</p>
<p>elevenlabs = ElevenLabs()</p>
<p>audio = elevenlabs.text_to_speech.convert(</p>
<p className="pl-4">
voice_id="
<SpeechSelect
ariaLabel="Voice"
value={voiceId}
items={speechVoices}
onChange={setVoiceId}
menuWidth={304}
inlineCode
triggerValue={voiceId}
/>
",
</p>
<p className="flex min-w-0 items-center pl-4">
<span className="whitespace-pre">text="</span>
<ApiPromptInput value={prompt} onChange={setPrompt} />
<span className="whitespace-pre">",</span>
</p>
<p className="pl-4">model_id="eleven_v3",</p>
<p className="pl-4">
language_code="
<SpeechSelect
ariaLabel="Language"
value={language}
items={speechLanguageItems}
onChange={setLanguage}
menuWidth={222}
inlineCode
triggerValue={language}
/>
",
</p>
<p>)</p>
</>
)}
</div>
<div className="flex h-12 flex-none items-center gap-1 px-3 pb-3">
<button
type="button"
aria-label={copied ? 'Copied' : 'Copy code'}
onClick={copyCode}
className={cn(
'flex size-9 flex-none items-center justify-center rounded-full text-[#59544f] transition hover:text-black',
focus,
)}
>
<CopyIcon copied={copied} />
</button>
<CodeLanguageSelect value={codeLanguage} onChange={setCodeLanguage} />
<button
type="button"
onClick={runCode}
disabled={loading}
className={cn(
'ml-auto flex h-9 items-center justify-center rounded-full bg-black px-3.5 text-[14px] leading-none text-white transition hover:bg-[#59544f] disabled:cursor-default',
focus,
)}
>
{loading ? (
<SpeechLoadingSpinner />
) : playing ? (
'Pause'
) : (
<>
<span className="sm:hidden">Run</span>
<span className="hidden sm:contents">Run code</span>
</>
)}
</button>
</div>
<audio
ref={audioRef}
src={selectedVoice.preview}
preload="none"
className="hidden"
onPlaying={() => {
setLoading(false)
setPlaying(true)
}}
onWaiting={() => setLoading(true)}
onPause={() => {
setLoading(false)
setPlaying(false)
}}
onEnded={() => {
setLoading(false)
setPlaying(false)
}}
/>
</div>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/product-tabs/creative/music.tsx
'use client'
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { getImage } from '../../../data'
import { CompactPlayButton } from '../../../components/shared'
export function CreativeMusicDemo() {
const [playing, setPlaying] = useState(false)
return (
<div className="absolute top-[106px] left-1/2 z-[5] flex h-[228px] w-[calc(100%-32px)] max-w-[592px] -translate-x-1/2 flex-col overflow-hidden rounded-3xl bg-white shadow-[inset_0_0_0_.5px_rgb(0_0_0_/_0.075)] md:top-[115px]">
<div className="min-h-0 flex-1 overflow-y-auto px-5 pt-4 text-[16px] leading-6 text-black [scrollbar-width:none]">
A rich orchestral track, deeply cinematic, symphonic strings, brass and woodwinds, an epic fantasy, triumphant,
jubilant, crescendo, finale.
</div>
<div className="relative z-10 flex items-end justify-between gap-4 px-3 pb-3">
<div className="flex h-14 min-w-0 items-end gap-4">
<div className="flex size-14 flex-none items-center justify-center rounded-xl bg-[#f5f3f1]">
<div className="relative size-11 overflow-hidden rounded-full">
<img
src={getImage('creative-music-orb')}
alt=""
className={cn(
'size-full object-cover transition-transform duration-700',
playing ? 'animate-[spin_9s_linear_infinite] scale-110' : 'scale-100',
)}
/>
<span className="absolute inset-0 rounded-full ring-[.5px] ring-black/[.075] ring-inset" />
</div>
</div>
<div className="min-w-0">
<p className="truncate text-[16px] leading-6 font-medium text-black">Epic Symphony</p>
<p className="text-[16px] leading-6 font-medium text-[#a9a49e]">0:50</p>
</div>
</div>
<CompactPlayButton playing={playing} onClick={() => setPlaying((current) => !current)} iconOnly />
</div>
</div>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/product-tabs/creative/text-to-speech.tsx
'use client'
import { useEffect, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import { IconCheck } from '@tabler/icons-react'
import { Chevron, CompactPlayButton, PlayIcon, focus } from '../../../components/shared'
import { speechInitialText, speechVoices } from '../../../data'
import { speechLanguageItems } from '../../../data/flags'
import { SpeechSelect } from '../../../components/speech-select'
export function CreativeTextToSpeechDemo() {
const [speechText, setSpeechText] = useState(speechInitialText)
const [language, setLanguage] = useState('en')
const [voiceId, setVoiceId] = useState<string>(speechVoices[0].id)
const [voicePage, setVoicePage] = useState(0)
const [playing, setPlaying] = useState(false)
const [loading, setLoading] = useState(false)
const [playAfterVoiceSelection, setPlayAfterVoiceSelection] = useState(false)
const audioRef = useRef<HTMLAudioElement>(null)
const selectedVoice = speechVoices.find((voice) => voice.id === voiceId) ?? speechVoices[0]
const visibleVoices = speechVoices.slice(voicePage * 5, voicePage * 5 + 5)
const lastVoicePage = Math.ceil(speechVoices.length / 5) - 1
const highlightedText = speechText.split(/(\[[^\]]+\])/g)
useEffect(() => {
const audio = audioRef.current
if (!audio || !playAfterVoiceSelection) return
setPlayAfterVoiceSelection(false)
setLoading(true)
void audio
.play()
.then(() => {
setLoading(false)
setPlaying(true)
})
.catch(() => {
setLoading(false)
setPlaying(false)
})
}, [playAfterVoiceSelection, voiceId])
const toggle = () => {
const audio = audioRef.current
if (!audio || loading) return
if (audio.paused) {
setLoading(true)
void audio
.play()
.then(() => {
setLoading(false)
setPlaying(true)
})
.catch(() => {
setLoading(false)
setPlaying(false)
})
} else {
audio.pause()
setLoading(false)
setPlaying(false)
}
}
return (
<div className="absolute top-[68px] left-1/2 z-[5] flex h-80 w-[calc(100%-32px)] max-w-[832px] -translate-x-1/2 overflow-hidden rounded-3xl bg-white shadow-[inset_0_0_0_.5px_rgb(0_0_0_/_0.075)] md:top-[69px]">
<div className="hidden w-1/2 flex-col border-r border-black/[.075] md:flex">
<div className="h-[272px] overflow-hidden">
{visibleVoices.map((voice, index) => {
const selected = voice.id === voiceId
const edgeSpacing =
index === 0 ? 'h-[59px] pt-[11px]' : index === visibleVoices.length - 1 ? 'h-[59px] pb-[11px]' : 'h-12'
return (
<button
key={voice.id}
type="button"
onClick={() => {
if (selected) {
toggle()
return
}
audioRef.current?.pause()
setPlaying(false)
setLoading(false)
setVoiceId(voice.id)
setPlayAfterVoiceSelection(true)
}}
aria-label={selected ? `${playing ? 'Pause' : 'Play'} ${voice.label}` : `Play ${voice.label} preview`}
className={cn('group block w-full px-3 text-left', edgeSpacing, focus)}
>
<span
className={cn(
'flex h-12 w-full items-center gap-2.5 rounded-xl pr-4 pl-3 transition-colors hover:bg-[#f5f3f1]',
selected && 'bg-[#f5f3f1]',
)}
>
<span className="relative size-5 flex-none">
<img src={voice.image} alt="" className="size-full rounded-full object-cover" />
{selected && (
<span className="absolute -top-1 -right-1 flex size-3 items-center justify-center rounded-full bg-black text-white ring-2 ring-[#f5f3f1]">
<IconCheck className="size-2 text-white" strokeWidth={2.5} aria-hidden="true" />
</span>
)}
</span>
<span className="flex min-w-0 flex-1 items-center justify-between gap-3">
<span className="truncate text-[15px] leading-5 font-medium text-black">{voice.label}</span>
<span
className={cn(
'truncate text-[14px] leading-5 font-medium text-[#777169]',
selected ? 'hidden' : 'group-hover:hidden',
)}
>
{voice.description}
</span>
<span
className={cn(
'ml-auto size-9 flex-none items-center justify-center rounded-full bg-white text-black shadow-[0_0_1px_rgb(0_0_0_/_0.34),0_1px_2px_rgb(0_0_0_/_0.04),0_2px_4px_rgb(0_0_0_/_0.04)] [&>svg]:!size-4',
selected ? 'flex' : 'hidden group-hover:flex',
)}
>
<PlayIcon playing={selected && playing} />
</span>
</span>
</span>
</button>
)
})}
</div>
<div className="flex h-12 items-center justify-between px-4">
<button
type="button"
className={cn(
'inline-flex h-9 items-center rounded-full bg-white px-3.5 text-[14px] leading-5 text-black shadow-[0_0_1px_rgb(0_0_0_/_0.34),0_1px_2px_rgb(0_0_0_/_0.04),0_2px_4px_rgb(0_0_0_/_0.04)] transition hover:bg-[#fbfaf9]',
focus,
)}
>
Explore 10,000+ voices
</button>
<div className="flex items-center text-[#59544f]">
<button
type="button"
aria-label="Previous voices"
disabled={voicePage === 0}
onClick={() => setVoicePage((current) => Math.max(0, current - 1))}
className={cn(
'flex size-8 items-center justify-center rounded-full disabled:opacity-30 [&>svg]:size-4',
focus,
)}
>
<Chevron direction="previous" />
</button>
<button
type="button"
aria-label="Next voices"
disabled={voicePage === lastVoicePage}
onClick={() => setVoicePage((current) => Math.min(lastVoicePage, current + 1))}
className={cn(
'flex size-8 items-center justify-center rounded-full disabled:opacity-30 [&>svg]:size-4',
focus,
)}
>
<Chevron direction="next" />
</button>
</div>
</div>
</div>
<div className="flex min-w-0 flex-1 flex-col">
<div className="flex h-[272px] flex-col overflow-y-auto [scrollbar-width:none]">
<div className="pointer-events-none z-10 -mb-3 hidden h-11 flex-none items-end px-5 pb-3 md:flex">
<label htmlFor="creative-tts-text" className="truncate text-[12px] leading-[21px] text-[#a9a49e]">
Enter your own text
</label>
</div>
<div className="relative min-h-0 flex-1">
<div
aria-hidden="true"
className="absolute inset-0 overflow-hidden px-5 pt-3 text-[16px] leading-6 text-black whitespace-pre-wrap"
>
{highlightedText.map((part, index) =>
part.startsWith('[') ? (
<span key={`${part}-${index}`} className="text-[#a9a49e]">
{part}
</span>
) : (
part
),
)}
</div>
<textarea
id="creative-tts-text"
value={speechText}
onChange={(event) => setSpeechText(event.target.value)}
aria-label="Enter your own text"
spellCheck={false}
className={cn(
'absolute inset-0 size-full resize-none overflow-hidden bg-transparent px-5 pt-3 text-[16px] leading-6 text-transparent caret-black outline-none',
focus,
)}
/>
<div className="pointer-events-none absolute right-0 bottom-0 left-0 h-6 bg-gradient-to-t from-white" />
</div>
</div>
<div className="flex h-12 items-start gap-1 px-3 pb-3">
<SpeechSelect
ariaLabel="Language"
value={language}
items={speechLanguageItems}
onChange={setLanguage}
menuWidth={214}
/>
<div className="ml-auto">
<CompactPlayButton playing={playing} loading={loading} onClick={toggle} />
</div>
</div>
</div>
<audio
ref={audioRef}
src={selectedVoice.preview}
preload="none"
className="hidden"
onPlaying={() => {
setLoading(false)
setPlaying(true)
}}
onWaiting={() => setLoading(true)}
onPause={() => {
setLoading(false)
setPlaying(false)
}}
onEnded={() => {
setLoading(false)
setPlaying(false)
}}
/>
</div>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/product-tabs/creative/voice-generator.tsx
'use client'
import type { CSSProperties } from 'react'
import { useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import { OrbSmooth } from '@/components/orb/smooth/index'
import { voices } from '../../../data'
import { Chevron, PlayIcon, VoiceDetails, focus, buttonShadow } from '../../../components/shared'
export function VoiceGeneratorDemo() {
const [activeVoice, setActiveVoice] = useState(5)
const [isPlaying, setIsPlaying] = useState(false)
const [playAfterSelection, setPlayAfterSelection] = useState(false)
const [orbTextureReady, setOrbTextureReady] = useState<string | null>(null)
const audioRef = useRef<HTMLAudioElement>(null)
const selectVoice = (index: number) => {
const audio = audioRef.current
if (index !== activeVoice) {
audio?.pause()
setIsPlaying(false)
setActiveVoice(index)
setPlayAfterSelection(true)
return
}
if (!audio) return
if (audio.paused) {
void audio.play().then(() => setIsPlaying(true))
} else {
audio.pause()
setIsPlaying(false)
}
}
const moveVoice = (direction: -1 | 1) => {
const next = Math.min(voices.length - 1, Math.max(0, activeVoice + direction))
if (next === activeVoice) return
audioRef.current?.pause()
setIsPlaying(false)
setActiveVoice(next)
}
return (
<>
<div className="absolute inset-0 z-[2] overflow-hidden">
{voices.map((voice, index) => {
const offset = index - activeVoice
const distance = Math.abs(offset)
const direction = Math.sign(offset)
const desktopX =
distance === 0 ? 0 : direction * (distance === 1 ? 310 : distance === 2 ? 534 : 534 + (distance - 2) * 220)
const mobileX =
distance === 0 ? 0 : direction * (distance === 1 ? 200 : distance === 2 ? 292 : 292 + (distance - 2) * 120)
const desktopScale = distance === 0 ? 1 : distance === 1 ? 0.787 : distance === 2 ? 0.567 : 0.4
const mobileScale = distance === 0 ? 1 : distance === 1 ? 0.7 : distance === 2 ? 0.5 : 0.35
const opacity = distance === 0 ? 1 : distance === 1 ? 0.96 : distance === 2 ? 0.72 : 0
const orbStyle = {
'--orb-x': `${desktopX}px`,
'--orb-mobile-x': `${mobileX}px`,
'--orb-scale': desktopScale,
'--orb-mobile-scale': mobileScale,
opacity,
zIndex: Math.max(0, 3 - distance),
pointerEvents: distance <= 2 ? 'auto' : 'none',
} as CSSProperties
return (
<div
key={voice.name}
style={orbStyle}
className="group absolute top-[127px] left-1/2 size-[200px] origin-center [transform:translate3d(calc(-50%+var(--orb-mobile-x)),0,0)_scale(var(--orb-mobile-scale))] will-change-[transform,opacity] transition-[transform,opacity] duration-700 ease-[cubic-bezier(.22,1,.36,1)] lg:top-[65px] lg:size-64 lg:[transform:translate3d(calc(-50%+var(--orb-x)),0,0)_scale(var(--orb-scale))]"
>
<button
type="button"
aria-label={`${isPlaying && distance === 0 ? 'Pause' : 'Play'} ${voice.name} preview`}
onClick={() => selectVoice(index)}
className={cn('absolute inset-0 z-30 cursor-pointer rounded-full', focus)}
/>
<span className="pointer-events-none absolute -inset-1 scale-[.985] rounded-full border-4 border-[#ebe8e4] opacity-0 transition group-hover:scale-100 group-hover:opacity-100" />
<span className="absolute inset-0 overflow-hidden rounded-full">
<img src={voice.image} alt="" className="size-full object-cover" />
</span>
{distance === 0 && (
<div
aria-hidden="true"
className={cn(
'pointer-events-none absolute inset-0 z-10 overflow-hidden rounded-full transition-opacity duration-500',
isPlaying ? 'opacity-100' : 'opacity-0',
)}
>
<OrbSmooth
textureUrl={voice.image}
audioMode={isPlaying ? 'file' : 'ambient'}
audioElement={audioRef}
audioSrc={voice.preview}
sphereScale={0.9}
spherePower={1.1}
grainOpacity={0.42}
animated
onTextureReady={setOrbTextureReady}
size={256}
className="size-full"
/>
</div>
)}
{distance === 0 && (
<span
aria-hidden="true"
className={cn(
'pointer-events-none absolute top-1/2 left-1/2 z-20 inline-flex size-14 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-white text-black',
buttonShadow,
)}
>
<PlayIcon playing={isPlaying} />
</span>
)}
</div>
)
})}
<div
aria-hidden="true"
className="pointer-events-none absolute inset-y-0 left-0 z-10 hidden w-44 bg-gradient-to-r from-[#f5f3f1] to-transparent md:block"
/>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-y-0 right-0 z-10 hidden w-44 bg-gradient-to-l from-[#f5f3f1] to-transparent md:block"
/>
</div>
<div className="absolute top-[351px] right-4 left-4 z-[6] grid h-20 grid-cols-[44px_minmax(0,1fr)_44px] items-center gap-x-2 lg:hidden">
<button
type="button"
aria-label="Previous voice"
disabled={activeVoice === 0}
onClick={() => moveVoice(-1)}
className={cn(
'inline-flex h-10 w-11 items-center justify-center rounded-full text-[#59544f] disabled:pointer-events-none disabled:opacity-0',
focus,
)}
>
<Chevron direction="previous" />
</button>
<div className="relative h-20 min-w-0 overflow-hidden">
{voices.map((voice, index) => {
const offset = index - activeVoice
const distance = Math.abs(offset)
const detailStyle = {
'--detail-x': `${offset * 100}%`,
opacity: distance === 0 ? 1 : 0,
pointerEvents: distance === 0 ? 'auto' : 'none',
} as CSSProperties
return (
<div
key={voice.name}
style={detailStyle}
className="absolute top-0 left-1/2 h-20 w-full [transform:translate3d(calc(-50%+var(--detail-x)),0,0)] will-change-[transform,opacity] transition-[transform,opacity] duration-700 ease-[cubic-bezier(.22,1,.36,1)]"
>
<VoiceDetails voiceIndex={index} muted={distance !== 0} />
</div>
)
})}
</div>
<button
type="button"
aria-label="Next voice"
disabled={activeVoice === voices.length - 1}
onClick={() => moveVoice(1)}
className={cn(
'inline-flex h-10 w-11 items-center justify-center rounded-full text-[#59544f] disabled:pointer-events-none disabled:opacity-0',
focus,
)}
>
<Chevron direction="next" />
</button>
</div>
<div className="absolute top-[353px] left-1/2 z-[6] hidden h-20 w-[854px] -translate-x-1/2 overflow-hidden lg:block">
{voices.map((voice, index) => {
const offset = index - activeVoice
const distance = Math.abs(offset)
const detailStyle = {
'--detail-x': `${offset * 309}px`,
opacity: distance <= 1 ? 1 : 0,
pointerEvents: distance === 0 ? 'auto' : 'none',
} as CSSProperties
return (
<div
key={voice.name}
style={detailStyle}
className="absolute top-0 left-1/2 h-20 w-[236px] px-4 [transform:translate3d(calc(-50%+var(--detail-x)),0,0)] will-change-[transform,opacity] transition-[transform,opacity] duration-700 ease-[cubic-bezier(.22,1,.36,1)]"
>
<VoiceDetails voiceIndex={index} muted={distance !== 0} />
</div>
)
})}
<button
type="button"
aria-label="Previous voice"
disabled={activeVoice === 0}
onClick={() => moveVoice(-1)}
className={cn(
'absolute top-5 left-[247.5px] inline-flex h-10 w-[50px] items-center justify-center rounded-full text-[#59544f] disabled:pointer-events-none disabled:opacity-0',
focus,
)}
>
<Chevron direction="previous" />
</button>
<button
type="button"
aria-label="Next voice"
disabled={activeVoice === voices.length - 1}
onClick={() => moveVoice(1)}
className={cn(
'absolute top-5 left-[556.5px] inline-flex h-10 w-[50px] items-center justify-center rounded-full text-[#59544f] disabled:pointer-events-none disabled:opacity-0',
focus,
)}
>
<Chevron direction="next" />
</button>
</div>
<button
type="button"
className={cn(
'absolute bottom-5 left-1/2 z-20 inline-flex h-10 -translate-x-1/2 items-center justify-center rounded-full bg-black px-4 text-[15px] leading-[22px] whitespace-nowrap text-white transition hover:bg-[#393735] active:scale-[.98] lg:right-8 lg:bottom-6 lg:left-auto lg:translate-x-0',
focus,
)}
>
Sign up
</button>
<audio
ref={audioRef}
src={voices[activeVoice].preview}
crossOrigin="anonymous"
preload="none"
className="hidden"
onEnded={() => setIsPlaying(false)}
/>
</>
)
}
src/registry/blocks/eleven-labs/eleven-labs-1/sections/product-tabs/index.tsx
'use client'
import { useState } from 'react'
import { cn } from '@/lib/utils'
import { productFamilies, products, agentProducts, apiProducts } from '../../data'
import { focus, buttonShadow } from '../../components/shared'
import { VoiceGeneratorDemo } from './creative/voice-generator'
import { CreativeTextToSpeechDemo } from './creative/text-to-speech'
import { CreativeMusicDemo } from './creative/music'
import { AgentsDemo } from './agents'
import { ApiTextToSpeechDemo } from './api/text-to-speech'
export function ProductTabsSection() {
const [activeFamily, setActiveFamily] = useState<(typeof productFamilies)[number]['id']>('creative')
const [creativeProduct, setCreativeProduct] = useState<'voice-generator' | 'text-to-speech' | 'music'>(
'voice-generator',
)
const [agentProduct, setAgentProduct] = useState<'voice' | 'chat'>('voice')
return (
<>
<section
aria-labelledby="mastermind-title"
className="mx-auto w-[calc(100%-40px)] max-w-[1176px] pt-20 pb-6 md:w-[calc(100%-128px)] md:pt-[120px] md:pb-10"
>
<div className="grid grid-cols-1 lg:grid-cols-2 lg:gap-x-12">
<h1
id="mastermind-title"
className="m-0 max-w-lg [font-family:var(--font-waldenburg)] text-[36px] leading-[42px] font-light tracking-[-.72px] text-balance before:table before:mb-[-7.5px] before:content-[''] after:table after:mt-[-8.5px] after:content-[''] lg:col-start-1 lg:row-start-1 lg:max-w-none lg:text-[48px] lg:leading-[52px] lg:tracking-[-.96px] lg:before:mb-[-8.5px] lg:after:mt-[-9px]"
>
Bringing
<span className="table"> technology to life</span>
</h1>
<h2 className="mt-8 text-[16px] leading-6 font-normal tracking-[.16px] text-balance before:table before:mb-[-6px] before:content-[''] after:table after:mt-[-6.5px] after:content-[''] lg:col-start-2 lg:row-start-1 lg:mt-0 lg:self-end lg:text-pretty">
Powering the best enterprises, creators, and developers. From ElevenAgents for customer experience,
ElevenCreative for content creation, to the leading AI voice generator.
</h2>
<div className="mt-8 flex flex-wrap gap-2 lg:col-start-1 lg:row-start-2">
<button
type="button"
className={cn(
'inline-flex h-11 w-fit items-center justify-center rounded-full bg-black px-5 text-[16px] leading-6 whitespace-nowrap text-white transition hover:bg-[#393735] active:scale-[.98]',
focus,
)}
>
Sign up
</button>
<button
type="button"
className={cn(
'inline-flex h-11 w-fit items-center justify-center rounded-full bg-white px-5 text-[16px] leading-6 whitespace-nowrap text-black transition hover:bg-[#fbfaf9] active:scale-[.98]',
buttonShadow,
focus,
)}
>
Book a demo
</button>
</div>
</div>
</section>
<section
aria-label="ElevenLabs products"
className="relative mx-auto mt-20 w-[calc(100%-40px)] max-w-[1176px] pb-20 md:w-[calc(100%-128px)] lg:mt-[52px] lg:pb-[120px]"
>
<div className="absolute -top-[76px] -right-px -left-px z-40 h-[76px] rounded-full">
<div className="relative h-[76px] translate-y-5 overflow-hidden rounded-t-[20px] bg-[#f5f3f1] p-1.5 shadow-[inset_0_0_0_.5px_rgb(0_0_0_/_0.075)] before:pointer-events-none before:absolute before:inset-0 before:bg-black/[.01] before:content-[''] after:pointer-events-none after:absolute after:right-0 after:bottom-0 after:left-0 after:z-[3] after:h-5 after:rounded-t-[20px] after:bg-[#f5f3f1] after:content-['']">
<div role="tablist" aria-label="Product family" className="relative z-[2] grid h-11 grid-cols-3 isolate">
<span
aria-hidden="true"
className="absolute inset-y-0 left-0 -z-10 w-[calc(33.333333%-1px)] rounded-[14px] bg-white shadow-[0_0_1px_rgb(0_0_0_/_0.34),0_1px_2px_rgb(0_0_0_/_0.04)] transition-transform duration-500 ease-[cubic-bezier(.22,1,.36,1)]"
style={{
transform: `translateX(${productFamilies.findIndex((family) => family.id === activeFamily) * 100}%)`,
}}
/>
{productFamilies.map((family) => (
<button
key={family.id}
type="button"
role="tab"
aria-selected={family.id === activeFamily}
onClick={() => setActiveFamily(family.id)}
className={cn(
'relative z-[2] flex h-11 items-center justify-center gap-1.5 rounded-[14px] px-2 text-[16px] leading-6 transition-colors sm:px-5',
family.id === activeFamily ? 'text-black' : 'text-[#44403b] hover:text-black',
focus,
)}
>
<span className="relative mr-0.5 hidden size-3 overflow-hidden rounded-full bg-[#f5f3f1] min-[400px]:block">
<img src={family.icon} alt="" className="size-full object-cover" />
</span>
<span className="hidden sm:block">{family.label}</span>
<span className="sm:hidden">{family.shortLabel}</span>
</button>
))}
</div>
</div>
</div>
<div className="relative z-30 ml-[-1px] h-[558px] w-[calc(100%+2px)] overflow-hidden rounded-[20px] bg-[#f5f3f1] shadow-[inset_0_0_0_.5px_rgb(0_0_0_/_0.075)]">
<div className="absolute top-4 right-4 left-4 z-20 h-10 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden lg:top-auto lg:right-[145px] lg:bottom-6 lg:left-[145px] lg:flex lg:justify-center lg:overflow-visible">
{activeFamily === 'creative' && (
<div
role="tablist"
aria-label="Creative products"
className="flex h-10 w-max items-center whitespace-nowrap text-[#44403b]"
>
{products.map((product) => {
const isInternal =
product.id === 'voice-generator' || product.id === 'text-to-speech' || product.id === 'music'
const selected = product.id === creativeProduct
const tabClass = cn(
'h-10 flex-none rounded-full px-4 text-[15px] leading-10 transition hover:text-black',
focus,
selected && 'bg-white text-black shadow-[0_0_1px_rgb(0_0_0_/_0.34),0_1px_2px_rgb(0_0_0_/_0.04)]',
)
return isInternal ? (
<button
key={product.id}
type="button"
role="tab"
aria-selected={selected}
onClick={() => setCreativeProduct(product.id as 'voice-generator' | 'text-to-speech' | 'music')}
className={tabClass}
>
{product.title}
</button>
) : (
<button key={product.id} type="button" role="tab" aria-selected="false" className={tabClass}>
{product.title}
</button>
)
})}
</div>
)}
{activeFamily === 'agents' && (
<div
role="tablist"
aria-label="Agent channels"
className="flex h-10 w-max items-center whitespace-nowrap text-[#44403b]"
>
{agentProducts.map((product) => (
<button
key={product.id}
type="button"
role="tab"
aria-selected={product.id === agentProduct}
onClick={() => setAgentProduct(product.id)}
className={cn(
'h-10 flex-none rounded-full px-4 text-[15px] leading-10 transition hover:text-black',
focus,
product.id === agentProduct &&
'bg-white text-black shadow-[0_0_1px_rgb(0_0_0_/_0.34),0_1px_2px_rgb(0_0_0_/_0.04)]',
)}
>
{product.title}
</button>
))}
</div>
)}
{activeFamily === 'api' && (
<div
role="tablist"
aria-label="API products"
className="flex h-10 w-max items-center whitespace-nowrap text-[#44403b]"
>
{apiProducts.map((product) =>
product.id === 'text-to-speech' ? (
<button
key={product.id}
type="button"
role="tab"
aria-selected="true"
className={cn(
'h-10 flex-none rounded-full bg-white px-4 text-[15px] leading-10 text-black shadow-[0_0_1px_rgb(0_0_0_/_0.34),0_1px_2px_rgb(0_0_0_/_0.04)]',
focus,
)}
>
{product.title}
</button>
) : (
<button
key={product.id}
type="button"
role="tab"
aria-selected="false"
className={cn(
'h-10 flex-none rounded-full px-4 text-[15px] leading-10 transition hover:text-black',
focus,
)}
>
{product.title}
</button>
),
)}
</div>
)}
</div>
{activeFamily === 'creative' && creativeProduct === 'voice-generator' && <VoiceGeneratorDemo />}
{activeFamily === 'creative' && creativeProduct === 'text-to-speech' && <CreativeTextToSpeechDemo />}
{activeFamily === 'creative' && creativeProduct === 'music' && <CreativeMusicDemo />}
{activeFamily === 'agents' && <AgentsDemo mode={agentProduct} />}
{activeFamily === 'api' && <ApiTextToSpeechDemo />}
{(activeFamily !== 'creative' || creativeProduct !== 'voice-generator') && (
<button
type="button"
className={cn(
'absolute bottom-5 left-1/2 z-20 inline-flex h-10 -translate-x-1/2 items-center justify-center rounded-full bg-black px-4 text-[15px] leading-[22px] whitespace-nowrap text-white transition hover:bg-[#393735] active:scale-[.98] md:right-8 md:bottom-6 md:left-auto md:translate-x-0',
focus,
)}
>
{activeFamily === 'api' ? 'Get API key' : 'Sign up'}
</button>
)}
</div>
</section>
</>
)
}