shadimage
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&apos;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&apos;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]">&quot;@elevenlabs/elevenlabs-js&quot;</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(&quot;
              <SpeechSelect
                ariaLabel="Voice"
                value={voiceId}
                items={speechVoices}
                onChange={setVoiceId}
                menuWidth={304}
                inlineCode
                triggerValue={voiceId}
              />
              &quot;, {'{'}
            </p>
            <p className="flex min-w-0 items-center pl-8">
              <span className="whitespace-pre text-[#0a59d2]">text:</span>
              <span className="whitespace-pre"> &quot;</span>
              <ApiPromptInput value={prompt} onChange={setPrompt} />
              <span className="whitespace-pre">&quot;,</span>
            </p>
            <p className="pl-8">
              <span className="text-[#0a59d2]">modelId:</span>{' '}
              <span className="text-[#052f70]">&quot;eleven_v3&quot;</span>,
            </p>
            <p className="pl-8">
              <span className="text-[#0a59d2]">languageCode:</span> &quot;
              <SpeechSelect
                ariaLabel="Language"
                value={language}
                items={speechLanguageItems}
                onChange={setLanguage}
                menuWidth={222}
                inlineCode
                triggerValue={language}
              />
              &quot;,
            </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=&quot;
              <SpeechSelect
                ariaLabel="Voice"
                value={voiceId}
                items={speechVoices}
                onChange={setVoiceId}
                menuWidth={304}
                inlineCode
                triggerValue={voiceId}
              />
              &quot;,
            </p>
            <p className="flex min-w-0 items-center pl-4">
              <span className="whitespace-pre">text=&quot;</span>
              <ApiPromptInput value={prompt} onChange={setPrompt} />
              <span className="whitespace-pre">&quot;,</span>
            </p>
            <p className="pl-4">model_id=&quot;eleven_v3&quot;,</p>
            <p className="pl-4">
              language_code=&quot;
              <SpeechSelect
                ariaLabel="Language"
                value={language}
                items={speechLanguageItems}
                onChange={setLanguage}
                menuWidth={222}
                inlineCode
                triggerValue={language}
              />
              &quot;,
            </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>
    </>
  )
}

More from spaceui

Browse all components in this library →