{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "profile-1",
  "title": "Profile 1: public profile",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A public profile: cover and avatar art drawn from the handle, a verified badge, Follow that updates the count and undoes itself on failure, stats that count up, About, pointer-lit project cards and an Activity tab.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/animated-tabs.json",
    "https://ui.ballmac.com/r/avatar.json",
    "https://ui.ballmac.com/r/badge.json",
    "https://ui.ballmac.com/r/blur-fade.json",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/contribution-graph.json",
    "https://ui.ballmac.com/r/cover-art.json",
    "https://ui.ballmac.com/r/media.json",
    "https://ui.ballmac.com/r/number-ticker.json",
    "https://ui.ballmac.com/r/skeleton.json",
    "https://ui.ballmac.com/r/spotlight-card.json",
    "https://ui.ballmac.com/r/state-view.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/profile-1/profile-1.tsx",
      "content": "// Ballmac UI: Profile 1. https://ui.ballmac.com/blocks/profile-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowUpRight, BadgeCheck, CalendarDays, Check, FolderOpen, Link2, Mail, MapPin, Share2, Star, UserCheck, UserPlus } from \"lucide-react\"\n\nimport { AnimatedTabs, AnimatedTabsContent, AnimatedTabsList, AnimatedTabsTrigger } from \"@/components/ballmac/animated-tabs\"\nimport { Avatar, AvatarFallback, AvatarImage, type AvatarStatus } from \"@/components/ballmac/avatar\"\nimport { Badge } from \"@/components/ballmac/badge\"\nimport { BlurFade, BlurFadeGroup } from \"@/components/ballmac/blur-fade\"\nimport { Button } from \"@/components/ballmac/button\"\nimport { ContributionGraph, type ContributionDay } from \"@/components/ballmac/contribution-graph\"\nimport { CoverArt } from \"@/components/ballmac/cover-art\"\nimport { Media, type MediaSource } from \"@/components/ballmac/media\"\nimport { NumberTicker } from \"@/components/ballmac/number-ticker\"\nimport { Skeleton } from \"@/components/ballmac/skeleton\"\nimport { SpotlightCard } from \"@/components/ballmac/spotlight-card\"\nimport { frameProps, StateView, type StateProps } from \"@/components/ballmac/state-view\"\nimport { cn } from \"@/lib/utils\"\n\ntype Profile1Person = {\n  name: string\n  /** The handle without the @, also the seed of the generated cover. */\n  handle: string\n  /** One line under the name. */\n  headline?: string\n  bio?: string\n  location?: string\n  /** Already written for people (\"March 2021\"). */\n  joined?: string\n  verified?: boolean\n  /** A picture of the person, or leave out for initials over generated art. */\n  avatar?: string\n  avatarAlt?: string\n  /** A cover picture. Leave out for generated art. */\n  cover?: MediaSource\n  status?: AvatarStatus\n  /** A line about availability, shown with a green dot (\"Open to freelance work\"). */\n  availability?: string\n  followers: number\n  following: number\n  /** Whether the person using the page already follows them. */\n  isFollowing?: boolean\n  skills?: string[]\n  links?: { label: string; href: string; icon?: React.ReactNode }[]\n}\n\ntype Profile1Project = {\n  id: string\n  title: string\n  description: string\n  tags?: string[]\n  stars?: number\n  /** Already written for people (\"Updated 3 days ago\"). */\n  updated?: string\n  href?: string\n  cover?: MediaSource\n}\n\ntype Profile1Event = { id: string; text: string; time: string }\n\ntype Profile1Props = Omit<React.ComponentProps<\"section\">, \"title\"> &\n  StateProps & {\n    person?: Profile1Person\n    projects?: Profile1Project[]\n    /** One value per day, in calendar order, for the Activity tab. */\n    activity?: ContributionDay[]\n    /** Recent activity, newest first. */\n    recent?: Profile1Event[]\n    /** Called when Follow or Following is pressed with the new state. Throw to undo it. */\n    onFollow?: (following: boolean) => void | Promise<void>\n    /** Called by Message. Without it the button is hidden. */\n    onMessage?: () => void\n    /** Called by Share. Defaults to copying the page address. */\n    onShare?: () => void\n    /** BCP 47 locale for numbers and dates. */\n    locale?: string\n    /** The tab shown first. */\n    defaultTab?: \"work\" | \"activity\"\n  }\n\n/** A year of sample activity: arithmetic only, so every render and every browser draws the same days. */\nfunction sampleActivity(): ContributionDay[] {\n  const start = Date.UTC(2026, 1, 8)\n  return Array.from({ length: 36 * 7 }, (_, i) => {\n    const r = (Math.imul(i + 11, 2654435761) >>> 0) % 100\n    const weekend = i % 7 === 0 || i % 7 === 6\n    const count = weekend ? (r < 70 ? 0 : 1 + (r % 3)) : r < 22 ? 0 : r < 55 ? 1 + (r % 2) : r < 85 ? 3 + (r % 4) : 8 + (r % 6)\n    return { date: new Date(start + i * 86400000).toISOString().slice(0, 10), count }\n  })\n}\n\nconst samplePerson: Profile1Person = {\n  name: \"Mara Ellison\",\n  handle: \"mara\",\n  headline: \"Product designer building calm tools for creative teams\",\n  bio: \"I design interfaces that get out of the way. Ten years across design systems, editors and onboarding, now working on tools for people who make things for a living. I write about craft and ship small open-source projects on weekends.\",\n  location: \"Lisbon, Portugal\",\n  joined: \"March 2021\",\n  verified: true,\n  availability: \"Open to freelance work from November\",\n  status: \"online\",\n  followers: 12840,\n  following: 312,\n  skills: [\"Design systems\", \"Prototyping\", \"Interaction design\", \"Accessibility\", \"Figma\", \"React\", \"User research\"],\n  links: [\n    { label: \"maraellison.com\", href: \"#\" },\n    { label: \"github.com/mara\", href: \"#\" },\n    { label: \"Read the newsletter\", href: \"#\" },\n  ],\n}\n\nconst sampleProjects: Profile1Project[] = [\n  { id: \"p1\", title: \"Quiet\", description: \"A distraction-free writing app with a command bar, versions and one-click publishing.\", tags: [\"Product\", \"React\"], stars: 2480, updated: \"Updated 3 days ago\", href: \"#\" },\n  { id: \"p2\", title: \"Gridwork\", description: \"A layout system for Figma and code that keeps designs and components in sync.\", tags: [\"Design system\"], stars: 1312, updated: \"Updated last week\", href: \"#\" },\n  { id: \"p3\", title: \"Field notes\", description: \"Interviews and findings from 60 conversations with independent designers.\", tags: [\"Research\"], stars: 904, updated: \"Updated 2 weeks ago\", href: \"#\" },\n  { id: \"p4\", title: \"Tidy\", description: \"A tiny CLI that renames and sorts exported assets using your naming rules.\", tags: [\"Tooling\", \"Node\"], stars: 640, updated: \"Updated last month\", href: \"#\" },\n  { id: \"p5\", title: \"Soft focus\", description: \"A color palette generator that checks contrast as you pick.\", tags: [\"Color\", \"Accessibility\"], stars: 517, updated: \"Updated 2 months ago\", href: \"#\" },\n  { id: \"p6\", title: \"Onboarding teardown\", description: \"Forty onboarding flows, annotated and compared on one page.\", tags: [\"Writing\"], stars: 388, updated: \"Updated 3 months ago\", href: \"#\" },\n]\n\nconst sampleRecent: Profile1Event[] = [\n  { id: \"e1\", text: \"Published Quiet 2.0 with versions and a faster editor\", time: \"3 days ago\" },\n  { id: \"e2\", text: \"Opened a pull request on Gridwork: mirror layouts for Arabic and Hebrew\", time: \"5 days ago\" },\n  { id: \"e3\", text: \"Posted “Why empty states deserve a designer” to the newsletter\", time: \"9 days ago\" },\n  { id: \"e4\", text: \"Starred Tidy and 3 other repositories\", time: \"2 weeks ago\" },\n]\n\nconst initials = (name: string) => name.split(\" \").map((w) => w[0]).join(\"\").slice(0, 2)\n\nfunction ProfileSkeleton() {\n  return (\n    <div className=\"grid gap-8\" aria-hidden=\"true\">\n      <div className=\"bg-card overflow-hidden rounded-3xl border\">\n        <Skeleton className=\"h-40 rounded-none sm:h-52\" />\n        <div className=\"px-5 pb-6 sm:px-8\">\n          <Skeleton className=\"-mt-14 size-28 rounded-full ring-4 ring-card sm:-mt-16 sm:size-32\" />\n          <Skeleton className=\"mt-4 h-8 w-56\" />\n          <Skeleton className=\"mt-2 h-4 w-72 max-w-full\" />\n        </div>\n      </div>\n      <div className=\"grid gap-4 sm:grid-cols-2\">\n        {[0, 1, 2, 3].map((i) => (\n          <Skeleton key={i} className=\"h-64 rounded-2xl\" />\n        ))}\n      </div>\n    </div>\n  )\n}\n\n/**\n * A public profile page: a cover and avatar with art drawn from the handle when there are no pictures, the name with its verified\n * badge, a Follow button that updates the follower count straight away and undoes itself if it fails, stats that count up, an\n * About card, project cards that light up under the pointer, and an Activity tab with a contribution graph.\n */\nfunction Profile1({\n  person = samplePerson,\n  projects = sampleProjects,\n  activity,\n  recent = sampleRecent,\n  onFollow,\n  onMessage,\n  onShare,\n  locale = \"en-US\",\n  defaultTab = \"work\",\n  state = \"ready\",\n  onRetry,\n  emptyState,\n  errorState,\n  className,\n  ...props\n}: Profile1Props) {\n  const id = React.useId()\n  const days = React.useMemo(() => activity ?? sampleActivity(), [activity])\n  const baseFollowing = person.isFollowing ?? false\n  const [following, setFollowing] = React.useState(baseFollowing)\n  const [copied, setCopied] = React.useState(false)\n  const [announce, setAnnounce] = React.useState(\"\")\n  const followers = person.followers + (following === baseFollowing ? 0 : following ? 1 : -1)\n  const contributions = days.reduce((sum, d) => sum + Math.max(0, d.count), 0)\n\n  React.useEffect(() => {\n    if (!copied) return\n    const t = setTimeout(() => setCopied(false), 1800)\n    return () => clearTimeout(t)\n  }, [copied])\n\n  async function toggleFollow() {\n    const next = !following\n    setFollowing(next)\n    setAnnounce(next ? `You are following ${person.name}` : `You unfollowed ${person.name}`)\n    try {\n      await onFollow?.(next)\n    } catch {\n      setFollowing(!next)\n      setAnnounce(`Could not update. You are ${!next ? \"still following\" : \"not following\"} ${person.name}`)\n    }\n  }\n\n  async function share() {\n    if (onShare) return onShare()\n    try {\n      await navigator.clipboard?.writeText(window.location.href)\n      setCopied(true)\n    } catch {}\n  }\n\n  const stats = [\n    { id: \"followers\", label: \"Followers\", value: followers },\n    { id: \"following\", label: \"Following\", value: person.following },\n    { id: \"projects\", label: \"Projects\", value: projects.length },\n    { id: \"contributions\", label: \"Contributions\", value: contributions },\n  ]\n\n  const about = (\n    <aside aria-labelledby={`${id}-about`} className=\"bg-card rounded-2xl border p-5\">\n      <h2 id={`${id}-about`} className=\"text-sm font-semibold\">\n        About\n      </h2>\n      {person.bio ? <p className=\"text-muted-foreground mt-2 text-sm leading-relaxed text-pretty\">{person.bio}</p> : null}\n      {person.skills?.length ? (\n        <>\n          <h3 className=\"mt-5 text-xs font-medium\">Skills</h3>\n          <ul className=\"mt-2 flex flex-wrap gap-1.5\">\n            {person.skills.map((s) => (\n              <li key={s}>\n                <Badge variant=\"secondary\" className=\"font-normal\">\n                  {s}\n                </Badge>\n              </li>\n            ))}\n          </ul>\n        </>\n      ) : null}\n      {person.links?.length ? (\n        <>\n          <h3 className=\"mt-5 text-xs font-medium\">Links</h3>\n          <ul className=\"mt-2 grid gap-0.5\">\n            {person.links.map((l) => (\n              <li key={l.label}>\n                <a\n                  href={l.href}\n                  className=\"text-foreground hover:bg-accent focus-visible:ring-ring/50 group/link -mx-2 flex items-center gap-2.5 rounded-lg px-2 py-1.5 text-sm outline-none focus-visible:ring-[3px] [&_svg]:size-4\"\n                >\n                  <span aria-hidden=\"true\" className=\"text-muted-foreground group-hover/link:text-foreground\">\n                    {l.icon ?? <Link2 />}\n                  </span>\n                  <span className=\"min-w-0 flex-1 truncate\">{l.label}</span>\n                  <ArrowUpRight aria-hidden=\"true\" className=\"text-muted-foreground size-3.5 opacity-0 transition-opacity group-hover/link:opacity-100 group-focus-visible/link:opacity-100 rtl:-scale-x-100\" />\n                </a>\n              </li>\n            ))}\n          </ul>\n        </>\n      ) : null}\n    </aside>\n  )\n\n  const content = (\n    <div className=\"grid gap-8\">\n      <BlurFade direction=\"up\" offset={10} duration={0.5}>\n        <header className=\"bg-card overflow-hidden rounded-3xl border shadow-xs\">\n          <div className=\"relative h-40 sm:h-52\">\n            <Media media={person.cover} alt=\"\" fill priority className=\"absolute inset-0\" fallback={<CoverArt seed={person.handle} variant=\"mesh\" grid className=\"absolute inset-0\" />} />\n            <div aria-hidden=\"true\" className=\"to-card/40 absolute inset-x-0 bottom-0 h-16 bg-gradient-to-b from-transparent\" />\n          </div>\n          <div className=\"px-5 pb-6 sm:px-8\">\n            <div className=\"flex flex-wrap items-end gap-x-6 gap-y-4\">\n              <Avatar className=\"ring-card -mt-14 size-28 shrink-0 text-3xl shadow-lg ring-4 sm:-mt-16 sm:size-32 sm:text-4xl\" status={person.status}>\n                {person.avatar ? <AvatarImage src={person.avatar} alt=\"\" /> : null}\n                <AvatarFallback className=\"relative overflow-hidden bg-transparent\">\n                  <CoverArt seed={person.handle} variant=\"mesh\" className=\"absolute inset-0\" />\n                  <span className=\"text-foreground relative font-semibold tracking-tight\">{initials(person.name)}</span>\n                </AvatarFallback>\n              </Avatar>\n              <div className=\"ms-auto flex flex-wrap items-center gap-2 pb-1\">\n                <Button variant=\"outline\" size=\"icon\" aria-label={copied ? \"Link copied\" : `Share ${person.name}’s profile`} onClick={share}>\n                  {copied ? <Check aria-hidden=\"true\" /> : <Share2 aria-hidden=\"true\" />}\n                </Button>\n                {onMessage ? (\n                  <Button variant=\"outline\" onClick={onMessage}>\n                    <Mail aria-hidden=\"true\" />\n                    Message\n                  </Button>\n                ) : null}\n                <Button variant={following ? \"outline\" : \"default\"} aria-pressed={following} onClick={toggleFollow} className=\"min-w-28\">\n                  {following ? <UserCheck aria-hidden=\"true\" /> : <UserPlus aria-hidden=\"true\" />}\n                  {following ? \"Following\" : \"Follow\"}\n                </Button>\n              </div>\n            </div>\n\n            <div className=\"mt-4\">\n              <h1 className=\"flex flex-wrap items-center gap-x-2 text-3xl font-semibold tracking-[-0.035em] sm:text-4xl\">\n                {person.name}\n                {person.verified ? (\n                  <span className=\"inline-flex\" role=\"img\" aria-label=\"Verified\">\n                    <BadgeCheck aria-hidden=\"true\" className=\"fill-primary text-primary-foreground size-6 sm:size-7\" />\n                  </span>\n                ) : null}\n              </h1>\n              <p className=\"text-muted-foreground mt-0.5 text-sm\">@{person.handle}</p>\n              {person.headline ? <p className=\"mt-3 max-w-2xl text-base text-pretty\">{person.headline}</p> : null}\n              <ul className=\"text-muted-foreground mt-3 flex flex-wrap items-center gap-x-5 gap-y-1.5 text-sm [&_svg]:size-4\">\n                {person.location ? (\n                  <li className=\"flex items-center gap-1.5\">\n                    <MapPin aria-hidden=\"true\" />\n                    {person.location}\n                  </li>\n                ) : null}\n                {person.joined ? (\n                  <li className=\"flex items-center gap-1.5\">\n                    <CalendarDays aria-hidden=\"true\" />\n                    Joined {person.joined}\n                  </li>\n                ) : null}\n                {person.availability ? (\n                  <li className=\"text-foreground flex items-center gap-2\">\n                    <span aria-hidden=\"true\" className=\"bg-chart-2 size-2 rounded-full\" />\n                    {person.availability}\n                  </li>\n                ) : null}\n              </ul>\n            </div>\n          </div>\n\n          <dl className=\"divide-border grid grid-cols-2 divide-x border-t sm:grid-cols-4 [&>div:nth-child(n+3)]:max-sm:border-t\">\n            {stats.map((s) => (\n              <div key={s.id} className=\"flex flex-col-reverse gap-0.5 px-5 py-4 sm:px-8 sm:py-5\">\n                <dt className=\"text-muted-foreground text-xs\">{s.label}</dt>\n                <dd className=\"text-2xl font-semibold tracking-tight tabular-nums\">\n                  <NumberTicker value={s.value} locale={locale} format={s.value >= 100000 ? { notation: \"compact\" } : undefined} />\n                </dd>\n              </div>\n            ))}\n          </dl>\n        </header>\n      </BlurFade>\n\n      <div className=\"grid gap-8 lg:grid-cols-[minmax(0,1fr)_19rem]\">\n        <BlurFade direction=\"up\" offset={10} duration={0.5} delay={0.08} className=\"min-w-0\">\n          <AnimatedTabs defaultValue={defaultTab} variant=\"underline\">\n            <AnimatedTabsList aria-label={`${person.name}’s profile`}>\n              <AnimatedTabsTrigger value=\"work\">\n                Work\n                <span className=\"bg-muted text-muted-foreground ms-1.5 rounded-full px-1.5 text-[11px] leading-5 tabular-nums\">{projects.length}</span>\n              </AnimatedTabsTrigger>\n              <AnimatedTabsTrigger value=\"activity\">Activity</AnimatedTabsTrigger>\n            </AnimatedTabsList>\n\n            <AnimatedTabsContent value=\"work\" className=\"mt-6\">\n              <StateView\n                state={state === \"empty\" || !projects.length ? \"empty\" : \"ready\"}\n                bare\n                empty={{\n                  title: emptyState?.title ?? \"No projects yet\",\n                  description: emptyState?.description ?? `${person.name} has not shared any work yet.`,\n                  icon: emptyState?.icon ?? <FolderOpen />,\n                  action: emptyState?.action,\n                }}\n              >\n                <BlurFadeGroup stagger={0.07} delay={0.1} className=\"grid gap-4 sm:grid-cols-2 [&>*]:h-full [&>*>*]:h-full\" item={{ direction: \"up\", offset: 12, blur: 6 }}>\n                  {projects.map((p, i) => (\n                    <SpotlightCard\n                      key={p.id}\n                      size={360}\n                      className=\"focus-within:ring-ring/50 group/card flex flex-col overflow-hidden rounded-2xl p-0 transition-shadow duration-200 focus-within:ring-[3px] hover:shadow-md\"\n                    >\n                      <div className=\"relative aspect-[16/9] overflow-hidden\">\n                        <Media media={p.cover} alt=\"\" fill className=\"absolute inset-0 transition-transform duration-500 ease-out group-hover/card:scale-[1.03] motion-reduce:transition-none motion-reduce:group-hover/card:scale-100\" fallback={<CoverArt seed={p.id} variant={([\"rings\", \"bars\", \"dots\", \"waves\", \"mesh\"] as const)[i % 5]} grid className=\"absolute inset-0\" />} />\n                      </div>\n                      <div className=\"flex flex-1 flex-col gap-3 p-5\">\n                        <div className=\"flex items-start gap-2\">\n                          <h3 className=\"flex-1 text-base font-semibold tracking-tight\">\n                            {p.href ? (\n                              <a href={p.href} className=\"outline-none after:absolute after:inset-0 after:content-['']\">\n                                {p.title}\n                              </a>\n                            ) : (\n                              p.title\n                            )}\n                          </h3>\n                          {p.href ? (\n                            <ArrowUpRight aria-hidden=\"true\" className=\"text-muted-foreground group-hover/card:text-foreground mt-0.5 size-4 shrink-0 transition-transform duration-200 group-hover/card:-translate-y-0.5 group-hover/card:translate-x-0.5 motion-reduce:transition-none motion-reduce:group-hover/card:translate-x-0 motion-reduce:group-hover/card:translate-y-0 rtl:-scale-x-100 rtl:group-hover/card:-translate-x-0.5\" />\n                          ) : null}\n                        </div>\n                        <p className=\"text-muted-foreground line-clamp-2 text-sm text-pretty\">{p.description}</p>\n                        <div className=\"mt-auto grid gap-3 pt-1\">\n                          {p.tags?.length ? (\n                            <ul className=\"flex flex-wrap gap-1.5\">\n                              {p.tags.map((t) => (\n                                <li key={t}>\n                                  <Badge variant=\"outline\" className=\"font-normal\">\n                                    {t}\n                                  </Badge>\n                                </li>\n                              ))}\n                            </ul>\n                          ) : null}\n                          <div className=\"text-muted-foreground flex items-center justify-between gap-3 border-t pt-3 text-xs tabular-nums\">\n                            {p.stars !== undefined ? (\n                              <span className=\"flex items-center gap-1.5\">\n                                <Star aria-hidden=\"true\" className=\"size-3.5\" />\n                                <span className=\"sr-only\">Stars:</span>{\" \"}\n                                {new Intl.NumberFormat(locale).format(p.stars)}\n                              </span>\n                            ) : (\n                              <span />\n                            )}\n                            {p.updated ? <span>{p.updated}</span> : null}\n                          </div>\n                        </div>\n                      </div>\n                    </SpotlightCard>\n                  ))}\n                </BlurFadeGroup>\n              </StateView>\n            </AnimatedTabsContent>\n\n            <AnimatedTabsContent value=\"activity\" className=\"mt-6\">\n              <div className=\"grid gap-8\">\n                <ContributionGraph data={days} label=\"Contributions\" />\n                {recent.length ? (\n                  <section aria-labelledby={`${id}-recent`}>\n                    <h2 id={`${id}-recent`} className=\"text-sm font-semibold\">\n                      Recent\n                    </h2>\n                    <ol className=\"border-border mt-4 ms-1.5 grid gap-5 border-s ps-6\">\n                      {recent.map((e) => (\n                        <li key={e.id} className=\"relative\">\n                          <span aria-hidden=\"true\" className=\"bg-card border-foreground/40 absolute top-1.5 -start-[1.9rem] size-2.5 rounded-full border-2\" />\n                          <p className=\"text-sm text-pretty\">{e.text}</p>\n                          <p className=\"text-muted-foreground mt-0.5 text-xs\">{e.time}</p>\n                        </li>\n                      ))}\n                    </ol>\n                  </section>\n                ) : null}\n              </div>\n            </AnimatedTabsContent>\n          </AnimatedTabs>\n        </BlurFade>\n        <BlurFade direction=\"up\" offset={10} duration={0.5} delay={0.16} className=\"order-first h-fit lg:sticky lg:top-6 lg:order-none\">\n          {about}\n        </BlurFade>\n      </div>\n    </div>\n  )\n\n  return (\n    <section data-slot=\"profile-1\" data-state={state} aria-label={`${person.name}’s profile`} className={cn(\"mx-auto w-full max-w-5xl px-4 py-8 sm:px-6\", className)} {...frameProps(props)}>\n      <StateView\n        state={state === \"loading\" || state === \"error\" ? state : \"ready\"}\n        onRetry={onRetry}\n        label=\"Loading the profile\"\n        loading={<ProfileSkeleton />}\n        error={{ title: errorState?.title ?? \"This profile could not be loaded\", description: errorState?.description ?? \"Check your connection and try again.\", ...errorState }}\n      >\n        {content}\n      </StateView>\n      <p aria-live=\"polite\" className=\"sr-only\">\n        {announce || (copied ? \"Link copied\" : \"\")}\n      </p>\n    </section>\n  )\n}\n\nexport { Profile1, type Profile1Props, type Profile1Person, type Profile1Project, type Profile1Event }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/profile-1/profile-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "profile",
      "user",
      "public profile",
      "portfolio",
      "follow",
      "social",
      "creator",
      "community",
      "activity"
    ],
    "version": "1.0.0",
    "updated": "2026-10-17",
    "tests": true,
    "ai": {
      "summary": "A public profile page. <Profile1 person={{ name, handle, headline, bio, location, joined, verified, avatar, cover, availability, followers, following, isFollowing, skills, links }} projects={[{ id, title, description, tags, stars, updated, href, cover }]} activity={[{ date, count }]} recent={[{ id, text, time }]} onFollow={async (next) => …} onMessage onShare />. Without images it draws cover and avatar art from the handle and each project's id. Follow is optimistic: throw from onFollow to undo it.",
      "whenToUse": [
        "The public page of a member, creator, designer or developer in a community or marketplace",
        "A portfolio-style profile with projects and activity"
      ],
      "whenNotToUse": [
        "Editing your own account (use settings-1)",
        "A company About page (use team-1)",
        "An article author box (use blog-post-1)"
      ],
      "composesWith": [
        "app-shell-1",
        "chat-1",
        "header-2",
        "footer-2"
      ],
      "a11y": [
        {
          "keys": "Tab",
          "action": "Follow is a toggle button (aria-pressed) and its result is announced politely; each project is one link with a single tab stop"
        },
        {
          "keys": "Arrow keys in Work and Activity",
          "action": "Move between the two tabs"
        },
        {
          "keys": "Motion",
          "action": "Entrances, the count-up and the card glow are skipped when reduced motion is on"
        }
      ],
      "customization": [
        "cover and avatar take a URL or an image with alt text; leave them out for generated art in the theme's chart colors",
        "defaultTab: 'work' | 'activity'; locale formats numbers",
        "Pass real daily counts as activity, one per day in calendar order"
      ]
    },
    "states": [
      {
        "id": "loading",
        "title": "Loading",
        "example": "profile-1-loading",
        "trigger": "state=\"loading\"",
        "note": "A skeleton of the cover, avatar and project cards, announced as loading."
      },
      {
        "id": "empty",
        "title": "No projects yet",
        "example": "profile-1-empty",
        "trigger": "state=\"empty\" (or projects={[]})",
        "note": "The profile shows as usual and the Work tab says nothing is shared yet; emptyState sets the copy."
      },
      {
        "id": "error",
        "title": "Could not load",
        "example": "profile-1-error",
        "trigger": "state=\"error\"",
        "note": "An alert with Try again (onRetry)."
      }
    ],
    "examples": [
      "profile-1-demo",
      "profile-1-following",
      "profile-1-loading",
      "profile-1-empty",
      "profile-1-error"
    ],
    "url": "https://ui.ballmac.com/components/profile-1"
  },
  "categories": [
    "blocks",
    "profile"
  ],
  "type": "registry:block"
}