{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bookmark-manager-grid",
  "title": "Bookmark Manager Grid",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/bookmark-manager-grid/BookmarkManagerGrid.tsx",
      "content": "'use client'\n\nimport { useMemo, useState } from 'react'\nimport {\n  Bookmark,\n  BookOpen,\n  Check,\n  CheckCircle2,\n  Copy,\n  ExternalLink,\n  Globe,\n  Layers,\n  Palette,\n  Plus,\n  Search,\n  Bot,\n  ShieldCheck,\n  Star,\n  Tag,\n  Type,\n  X,\n  Zap,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent } from '@/components/ui/card'\nimport {\n  Dialog,\n  DialogClose,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from '@/components/ui/dialog'\nimport { Input } from '@/components/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Separator } from '@/components/ui/separator'\n\nexport interface BookmarkItem {\n  id: string\n  title: string\n  url: string\n  domain: string\n  faviconUrl: string\n  previewImage: string\n  excerpt: string\n  collection: 'design-systems' | 'web-perf' | 'typography' | 'ai-tools' | 'security'\n  tags: string[]\n  isFavorite: boolean\n  savedAt: string\n}\n\nexport interface CollectionMeta {\n  id: string\n  name: string\n  count: number\n  iconName: 'bookmark' | 'palette' | 'zap' | 'type' | 'bot' | 'shield' | 'star'\n}\n\nexport interface BookmarkManagerGridProps {\n  className?: string\n}\n\nconst INITIAL_BOOKMARKS: BookmarkItem[] = [\n  {\n    id: 'bm-01',\n    title: 'OKLCH Color Picker & Converter',\n    url: 'https://oklch.com',\n    domain: 'oklch.com',\n    faviconUrl: 'https://images.unsplash.com/photo-1550684848-fac1c5b4e853?w=64&auto=format&fit=crop&q=80',\n    previewImage: 'https://images.unsplash.com/photo-1541701494587-cb58502866ab?w=800&auto=format&fit=crop&q=80',\n    excerpt:\n      'Interactive color space playground with gamut-mapping, APCA contrast ratios, and direct CSS/Tailwind OKLCH code generation.',\n    collection: 'design-systems',\n    tags: ['#oklch', '#tailwind', '#design-systems'],\n    isFavorite: true,\n    savedAt: 'Aug 18, 2026',\n  },\n  {\n    id: 'bm-02',\n    title: 'Reka UI Headless Vue Primitives',\n    url: 'https://reka-ui.com',\n    domain: 'reka-ui.com',\n    faviconUrl: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=64&auto=format&fit=crop&q=80',\n    previewImage: 'https://images.unsplash.com/photo-1507238691740-187a5b1d37b8?w=800&auto=format&fit=crop&q=80',\n    excerpt:\n      'Accessible, completely unstyled UI component primitives for Vue 3 with WAI-ARIA compliant keyboard focus and composite slots.',\n    collection: 'design-systems',\n    tags: ['#reka-ui', '#vue', '#design-systems'],\n    isFavorite: true,\n    savedAt: 'Aug 15, 2026',\n  },\n  {\n    id: 'bm-03',\n    title: 'Web Vitals Performance Playbook',\n    url: 'https://web.dev/vitals',\n    domain: 'web.dev',\n    faviconUrl: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=64&auto=format&fit=crop&q=80',\n    previewImage: 'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=800&auto=format&fit=crop&q=80',\n    excerpt:\n      'Diagnostic field guides for optimizing Interaction to Next Paint (INP), Largest Contentful Paint (LCP), and layout stability.',\n    collection: 'web-perf',\n    tags: ['#web-vitals', '#web-perf', '#astro'],\n    isFavorite: false,\n    savedAt: 'Aug 12, 2026',\n  },\n  {\n    id: 'bm-04',\n    title: 'Typewolf Font Trends & Pairings',\n    url: 'https://typewolf.com',\n    domain: 'typewolf.com',\n    faviconUrl: 'https://images.unsplash.com/photo-1455390582262-044cdead277a?w=64&auto=format&fit=crop&q=80',\n    previewImage: 'https://images.unsplash.com/photo-1516962215378-7fa2e137ae93?w=800&auto=format&fit=crop&q=80',\n    excerpt:\n      'The definitive guide to web typography, font pairings, lookbooks, variable font loading strategies, and typographic hierarchy.',\n    collection: 'typography',\n    tags: ['#typography', '#design-systems', '#react'],\n    isFavorite: true,\n    savedAt: 'Aug 09, 2026',\n  },\n  {\n    id: 'bm-05',\n    title: 'Vercel AI SDK 3.0 Documentation',\n    url: 'https://sdk.vercel.ai/docs',\n    domain: 'sdk.vercel.ai',\n    faviconUrl: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=64&auto=format&fit=crop&q=80',\n    previewImage: 'https://images.unsplash.com/photo-1677442136019-21780ecad995?w=800&auto=format&fit=crop&q=80',\n    excerpt:\n      'Universal TypeScript library for streaming chat completions, structured JSON generation, tool calling, and multi-model routing.',\n    collection: 'ai-tools',\n    tags: ['#ai-prompt', '#react', '#astro'],\n    isFavorite: false,\n    savedAt: 'Aug 06, 2026',\n  },\n  {\n    id: 'bm-06',\n    title: 'OWASP API Security Top 10 Guide',\n    url: 'https://owasp.org/API-Security',\n    domain: 'owasp.org',\n    faviconUrl: 'https://images.unsplash.com/photo-1563986768609-322da13575f3?w=64&auto=format&fit=crop&q=80',\n    previewImage: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=800&auto=format&fit=crop&q=80',\n    excerpt:\n      'Crucial security vectors for modern APIs including broken object authorization, rate limiting flaws, and JWT key hygiene.',\n    collection: 'security',\n    tags: ['#security', '#web-perf'],\n    isFavorite: false,\n    savedAt: 'Aug 03, 2026',\n  },\n]\n\nconst COLLECTIONS: CollectionMeta[] = [\n  { id: 'all', name: 'All Bookmarks', count: 142, iconName: 'bookmark' },\n  { id: 'design-systems', name: 'Design Systems', count: 28, iconName: 'palette' },\n  { id: 'web-perf', name: 'Web Performance', count: 19, iconName: 'zap' },\n  { id: 'typography', name: 'Typography & Fonts', count: 14, iconName: 'type' },\n  { id: 'ai-tools', name: 'AI & LLM Tools', count: 35, iconName: 'bot' },\n  { id: 'security', name: 'Security & Auth', count: 22, iconName: 'shield' },\n  { id: 'favorites', name: 'Favorites ★', count: 18, iconName: 'star' },\n]\n\nconst TAG_CLOUD = [\n  '#reka-ui',\n  '#tailwind',\n  '#oklch',\n  '#astro',\n  '#react',\n  '#vue',\n  '#typography',\n  '#web-vitals',\n  '#security',\n  '#ai-prompt',\n]\n\nexport function BookmarkManagerGrid({ className }: BookmarkManagerGridProps) {\n  const [bookmarks, setBookmarks] = useState<BookmarkItem[]>([...INITIAL_BOOKMARKS])\n  const [searchQuery, setSearchQuery] = useState('')\n  const [selectedCollection, setSelectedCollection] = useState('all')\n  const [selectedTag, setSelectedTag] = useState<string | null>(null)\n\n  // Add Bookmark Modal state\n  const [isAddModalOpen, setIsAddModalOpen] = useState(false)\n  const [formUrl, setFormUrl] = useState('')\n  const [formTitle, setFormTitle] = useState('')\n  const [formDomain, setFormDomain] = useState('')\n  const [formExcerpt, setFormExcerpt] = useState('')\n  const [formCollection, setFormCollection] = useState<\n    'design-systems' | 'web-perf' | 'typography' | 'ai-tools' | 'security'\n  >('design-systems')\n  const [formTags, setFormTags] = useState('')\n  const [formPreview, setFormPreview] = useState('')\n\n  // Toast Notification\n  const [notificationToast, setNotificationToast] = useState<string | null>(null)\n  const [copiedId, setCopiedId] = useState<string | null>(null)\n\n  const showNotification = (msg: string) => {\n    setNotificationToast(msg)\n    setTimeout(() => {\n      setNotificationToast(null)\n    }, 2800)\n  }\n\n  const copyUrl = (item: BookmarkItem, event?: React.MouseEvent) => {\n    if (event) event.stopPropagation()\n    navigator.clipboard?.writeText(item.url)\n    setCopiedId(item.id)\n    showNotification(`Copied link to clipboard: ${item.domain}`)\n    setTimeout(() => {\n      setCopiedId(null)\n    }, 2000)\n  }\n\n  const toggleFavorite = (item: BookmarkItem, event?: React.MouseEvent) => {\n    if (event) event.stopPropagation()\n    setBookmarks((prev) =>\n      prev.map((bm) => {\n        if (bm.id === item.id) {\n          const nextFav = !bm.isFavorite\n          showNotification(nextFav ? `Starred \"${bm.title}\"` : `Removed \"${bm.title}\" from favorites`)\n          return { ...bm, isFavorite: nextFav }\n        }\n        return bm\n      }),\n    )\n  }\n\n  const toggleTag = (tag: string) => {\n    setSelectedTag((prev) => (prev === tag ? null : tag))\n  }\n\n  const resetFilters = () => {\n    setSearchQuery('')\n    setSelectedCollection('all')\n    setSelectedTag(null)\n  }\n\n  const autoFetchMetadata = () => {\n    const url = formUrl.trim()\n    if (!url) {\n      showNotification('Please enter a URL first')\n      return\n    }\n\n    let extractedDomain = ''\n    try {\n      const parsed = new URL(url.startsWith('http') ? url : `https://${url}`)\n      extractedDomain = parsed.hostname.replace(/^www\\./, '')\n    } catch {\n      extractedDomain = 'developer.mozilla.org'\n    }\n\n    setFormDomain(extractedDomain)\n\n    if (url.includes('github') || extractedDomain.includes('github')) {\n      setFormTitle('GitHub Engineering Architecture Guides')\n      setFormExcerpt('In-depth architectural post-mortems, system scaling principles, and resilient CI/CD pipelines.')\n      setFormCollection('web-perf')\n      setFormTags('#web-perf, #security')\n      setFormPreview('https://images.unsplash.com/photo-1618401471353-b98afee0b2eb?w=800&auto=format&fit=crop&q=80')\n    } else if (url.includes('astro') || extractedDomain.includes('astro')) {\n      setFormTitle('Astro 4.0 Server Islands & SSG Docs')\n      setFormExcerpt(\n        'Zero-JS by default frontend framework combining island architecture with fast static HTML builds.',\n      )\n      setFormCollection('design-systems')\n      setFormTags('#astro, #react, #vue')\n      setFormPreview('https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=800&auto=format&fit=crop&q=80')\n    } else if (url.includes('linear') || extractedDomain.includes('linear')) {\n      setFormTitle('Linear Method — Principles for Modern Software')\n      setFormExcerpt(\n        'Practices and philosophies for high-craft product execution, issue triage, and keyboard-first UI.',\n      )\n      setFormCollection('design-systems')\n      setFormTags('#design-systems, #tailwind')\n      setFormPreview('https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800&auto=format&fit=crop&q=80')\n    } else {\n      setFormTitle(formTitle || `${extractedDomain.toUpperCase()} — Resource Guide`)\n      setFormExcerpt(\n        formExcerpt || 'Curated documentation and developer tooling reference for modern engineering workflows.',\n      )\n      setFormTags(formTags || '#oklch, #tailwind')\n      setFormPreview(\n        formPreview || 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=800&auto=format&fit=crop&q=80',\n      )\n    }\n\n    showNotification(`Fetched metadata from ${extractedDomain}`)\n  }\n\n  const handleSaveBookmark = (e: React.FormEvent) => {\n    e.preventDefault()\n    if (!formUrl.trim()) {\n      showNotification('Please enter a valid URL')\n      return\n    }\n\n    const finalTitle = formTitle.trim() || formDomain.trim() || 'Untitled Bookmark'\n    let cleanDomain = formDomain.trim()\n    if (!cleanDomain) {\n      try {\n        const parsed = new URL(formUrl.startsWith('http') ? formUrl : `https://${formUrl}`)\n        cleanDomain = parsed.hostname.replace(/^www\\./, '')\n      } catch {\n        cleanDomain = 'web.dev'\n      }\n    }\n\n    const tagList = formTags\n      ? formTags\n          .split(',')\n          .map((t) => t.trim())\n          .filter(Boolean)\n          .map((t) => (t.startsWith('#') ? t : `#${t}`))\n      : ['#design-systems']\n\n    const newBookmark: BookmarkItem = {\n      id: `bm-${Date.now()}`,\n      title: finalTitle,\n      url: formUrl.startsWith('http') ? formUrl : `https://${formUrl}`,\n      domain: cleanDomain,\n      faviconUrl: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=64&auto=format&fit=crop&q=80',\n      previewImage:\n        formPreview || 'https://images.unsplash.com/photo-1541701494587-cb58502866ab?w=800&auto=format&fit=crop&q=80',\n      excerpt: formExcerpt.trim() || 'Developer documentation, components, and reference guides.',\n      collection: formCollection,\n      tags: tagList,\n      isFavorite: false,\n      savedAt: 'Just now',\n    }\n\n    setBookmarks((prev) => [newBookmark, ...prev])\n    setIsAddModalOpen(false)\n\n    // Reset form\n    setFormUrl('')\n    setFormTitle('')\n    setFormDomain('')\n    setFormExcerpt('')\n    setFormTags('')\n    setFormPreview('')\n\n    showNotification(`Saved \"${finalTitle}\" to bookmarks!`)\n  }\n\n  const filteredBookmarks = useMemo(() => {\n    return bookmarks.filter((item) => {\n      if (selectedCollection === 'favorites') {\n        if (!item.isFavorite) return false\n      } else if (selectedCollection !== 'all') {\n        if (item.collection !== selectedCollection) return false\n      }\n\n      if (selectedTag && !item.tags.includes(selectedTag)) {\n        return false\n      }\n\n      if (searchQuery.trim() !== '') {\n        const q = searchQuery.toLowerCase().trim()\n        const titleMatch = item.title.toLowerCase().includes(q)\n        const domainMatch = item.domain.toLowerCase().includes(q)\n        const excerptMatch = item.excerpt.toLowerCase().includes(q)\n        const urlMatch = item.url.toLowerCase().includes(q)\n        const tagMatch = item.tags.some((t) => t.toLowerCase().includes(q))\n        if (!titleMatch && !domainMatch && !excerptMatch && !urlMatch && !tagMatch) {\n          return false\n        }\n      }\n\n      return true\n    })\n  }, [bookmarks, selectedCollection, selectedTag, searchQuery])\n\n  const activeCollectionMeta = useMemo(() => {\n    return COLLECTIONS.find((c) => c.id === selectedCollection) || COLLECTIONS[0]\n  }, [selectedCollection])\n\n  return (\n    <div data-slot=\"bookmark-manager-grid\" className={cn('w-full space-y-6', className)}>\n      {/* Top Floating Toast Notification */}\n      {notificationToast && (\n        <div\n          role=\"status\"\n          aria-live=\"polite\"\n          className=\"bg-foreground text-background animate-in fade-in slide-in-from-top-2 fixed top-6 right-6 z-50 flex items-center gap-2.5 rounded-lg px-4 py-2.5 text-xs font-medium shadow-lg transition-colors\"\n        >\n          <CheckCircle2 className=\"text-primary size-4 shrink-0\" />\n          <span>{notificationToast}</span>\n        </div>\n      )}\n\n      {/* Header Section */}\n      <div className=\"bg-card text-card-foreground border-border/80 flex flex-col gap-5 rounded-2xl border p-5 shadow-xs sm:flex-row sm:items-center sm:justify-between lg:p-6\">\n        <div className=\"space-y-1.5\">\n          <div className=\"flex flex-wrap items-center gap-2.5\">\n            <div className=\"bg-primary/10 text-primary grid size-9 place-items-center rounded-xl shadow-xs\">\n              <Bookmark className=\"size-5\" />\n            </div>\n            <h1 className=\"text-xl font-bold tracking-tight sm:text-2xl\">Bookmarks & Engineering Resources</h1>\n            <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n              v2.1\n            </Badge>\n          </div>\n          <p className=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs\">\n            <Layers className=\"size-3.5\" />\n            <span className=\"text-foreground font-medium\">142 bookmarks · 8 collections</span>\n            <span className=\"text-border hidden sm:inline\">|</span>\n            <span className=\"text-muted-foreground hidden sm:inline\">Raindrop Sync Active</span>\n            <span className=\"bg-success inline-block size-1.5 rounded-full\" />\n          </p>\n        </div>\n\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n          {/* Search Input */}\n          <div className=\"relative w-full sm:w-64 lg:w-72\">\n            <Search\n              aria-hidden=\"true\"\n              className=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2\"\n            />\n            <Input\n              value={searchQuery}\n              onChange={(e) => setSearchQuery(e.target.value)}\n              type=\"search\"\n              placeholder=\"Search bookmarks, domains, tags…\"\n              className=\"h-9 pr-8 pl-9 text-xs\"\n            />\n            {searchQuery && (\n              <button\n                type=\"button\"\n                className=\"text-muted-foreground hover:text-foreground absolute top-1/2 right-2.5 min-h-6 -translate-y-1/2 text-xs\"\n                aria-label=\"Clear search\"\n                onClick={() => setSearchQuery('')}\n              >\n                <X className=\"size-3.5\" />\n              </button>\n            )}\n          </div>\n\n          {/* Add Bookmark Button */}\n          <Button\n            size=\"sm\"\n            className=\"shrink-0 font-medium active:scale-[0.98]\"\n            onClick={() => setIsAddModalOpen(true)}\n          >\n            <Plus className=\"size-4\" />\n            Add Bookmark\n          </Button>\n        </div>\n      </div>\n\n      {/* Mobile Horizontal Collections Bar (Visible on mobile/tablet) */}\n      <div className=\"bg-card text-card-foreground border-border/80 flex items-center gap-1.5 overflow-x-auto rounded-xl border p-2 shadow-xs lg:hidden\">\n        {COLLECTIONS.map((col) => (\n          <button\n            key={col.id}\n            type=\"button\"\n            className={cn(\n              'focus-visible:ring-ring flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium whitespace-nowrap transition-[colors,transform] duration-150 focus-visible:ring-2 focus-visible:outline-none active:scale-[0.98]',\n              selectedCollection === col.id\n                ? 'bg-primary text-primary-foreground shadow-xs'\n                : 'bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground',\n            )}\n            onClick={() => setSelectedCollection(col.id)}\n          >\n            <span>{col.name}</span>\n            <span\n              className={cn(\n                'py-0.2 rounded-full px-1.5 font-mono text-xs tabular-nums',\n                selectedCollection === col.id\n                  ? 'bg-primary-foreground/20 text-primary-foreground'\n                  : 'bg-background/80 text-muted-foreground',\n              )}\n            >\n              {col.count}\n            </span>\n          </button>\n        ))}\n      </div>\n\n      {/* 2-Column Main Layout (Left: Sidebar w-60; Right: Grid) */}\n      <div className=\"flex flex-col gap-6 lg:flex-row lg:items-start\">\n        {/* Left Collections Sidebar */}\n        <aside className=\"hidden w-60 shrink-0 space-y-6 lg:block\" aria-label=\"Collections and Tags\">\n          {/* Collections List Card */}\n          <div className=\"bg-card text-card-foreground border-border/80 space-y-3 rounded-2xl border p-4 shadow-xs\">\n            <div className=\"flex items-center justify-between px-1\">\n              <h2 className=\"text-foreground text-xs font-semibold tracking-wider uppercase\">Collections</h2>\n              <span className=\"text-muted-foreground font-mono text-xs tabular-nums\">8 folders</span>\n            </div>\n\n            <nav className=\"space-y-1\" aria-label=\"Collection folders\">\n              {COLLECTIONS.map((col) => (\n                <button\n                  key={col.id}\n                  type=\"button\"\n                  className={cn(\n                    'focus-visible:ring-ring flex w-full items-center justify-between rounded-lg px-3 py-2 text-xs font-medium transition-[color,background-color,box-shadow,transform] duration-150 focus-visible:ring-2 focus-visible:outline-none active:scale-[0.98]',\n                    selectedCollection === col.id\n                      ? 'bg-primary/10 text-primary border-primary/20 border font-semibold shadow-xs'\n                      : 'text-muted-foreground hover:bg-muted/70 hover:text-foreground border border-transparent',\n                  )}\n                  onClick={() => setSelectedCollection(col.id)}\n                >\n                  <div className=\"flex items-center gap-2.5 truncate\">\n                    {col.iconName === 'bookmark' && <Bookmark className=\"size-4 shrink-0\" />}\n                    {col.iconName === 'palette' && <Palette className=\"text-chart-1 size-4 shrink-0\" />}\n                    {col.iconName === 'zap' && <Zap className=\"text-warning size-4 shrink-0\" />}\n                    {col.iconName === 'type' && <Type className=\"text-info size-4 shrink-0\" />}\n                    {col.iconName === 'bot' && <Bot className=\"text-success size-4 shrink-0\" />}\n                    {col.iconName === 'shield' && <ShieldCheck className=\"text-destructive size-4 shrink-0\" />}\n                    {col.iconName === 'star' && <Star className=\"fill-warning text-warning size-4 shrink-0\" />}\n                    <span className=\"truncate\">{col.name}</span>\n                  </div>\n                  <span\n                    className={cn(\n                      'rounded-md px-1.5 py-0.5 font-mono text-xs tabular-nums',\n                      selectedCollection === col.id\n                        ? 'bg-primary/20 text-primary font-bold'\n                        : 'bg-muted/60 text-muted-foreground',\n                    )}\n                  >\n                    {col.count}\n                  </span>\n                </button>\n              ))}\n            </nav>\n          </div>\n\n          {/* Tags Cloud Card */}\n          <div className=\"bg-card text-card-foreground border-border/80 space-y-3 rounded-2xl border p-4 shadow-xs\">\n            <div className=\"flex items-center justify-between px-1\">\n              <h2 className=\"text-foreground flex items-center gap-1.5 text-xs font-semibold tracking-wider uppercase\">\n                <Tag className=\"text-primary size-3.5\" />\n                Tag Cloud\n              </h2>\n              {selectedTag && (\n                <button\n                  type=\"button\"\n                  className=\"text-primary min-h-6 text-xs transition-transform duration-150 hover:underline active:scale-95\"\n                  onClick={() => setSelectedTag(null)}\n                >\n                  Reset\n                </button>\n              )}\n            </div>\n\n            <div className=\"flex flex-wrap gap-1.5\">\n              {TAG_CLOUD.map((tag) => (\n                <button\n                  key={tag}\n                  type=\"button\"\n                  className={cn(\n                    'focus-visible:ring-ring min-h-6 rounded-md border px-2 py-0.5 text-xs font-medium transition-[colors,transform] duration-150 focus-visible:ring-2 focus-visible:outline-none active:scale-95',\n                    selectedTag === tag\n                      ? 'border-primary bg-primary/10 text-primary font-semibold shadow-xs'\n                      : 'border-border bg-background text-muted-foreground hover:border-foreground/30 hover:text-foreground',\n                  )}\n                  onClick={() => toggleTag(tag)}\n                >\n                  {tag}\n                </button>\n              ))}\n            </div>\n          </div>\n\n          {/* Quick Storage Vault Info Box */}\n          <div className=\"bg-muted/40 border-border/70 text-card-foreground space-y-2 rounded-2xl border p-4 text-xs\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"bg-primary/10 text-primary grid size-6 place-items-center rounded-md\">\n                <Globe className=\"size-3.5\" />\n              </div>\n              <span className=\"text-foreground font-semibold\">Raindrop Cloud Vault</span>\n            </div>\n            <p className=\"text-muted-foreground text-xs leading-relaxed\">\n              Encrypted cross-device bookmark vault with automated favicon caching and full-text metadata indexing.\n            </p>\n          </div>\n        </aside>\n\n        {/* Right Bookmarks Main Area */}\n        <main className=\"min-w-0 flex-1 space-y-4\">\n          {/* Result Bar & Filter Indicators */}\n          <div className=\"bg-card text-card-foreground border-border/80 flex flex-wrap items-center justify-between gap-3 rounded-xl border px-4 py-3 shadow-xs\">\n            <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n              <span className=\"text-muted-foreground\">Collection:</span>\n              <Badge variant=\"outline\" className=\"border-primary/40 bg-primary/5 text-primary text-xs font-medium\">\n                {activeCollectionMeta.name}\n              </Badge>\n\n              {selectedTag && (\n                <>\n                  <span className=\"text-muted-foreground ml-1\">Tag:</span>\n                  <Badge variant=\"secondary\" className=\"text-xs font-medium\">\n                    {selectedTag}\n                    <button\n                      type=\"button\"\n                      className=\"text-muted-foreground hover:text-foreground ml-1 inline-flex items-center\"\n                      aria-label=\"Remove tag filter\"\n                      onClick={() => setSelectedTag(null)}\n                    >\n                      <X className=\"size-3\" />\n                    </button>\n                  </Badge>\n                </>\n              )}\n\n              {searchQuery && (\n                <>\n                  <span className=\"text-muted-foreground ml-1\">Query:</span>\n                  <span className=\"text-foreground font-mono font-medium\">&ldquo;{searchQuery}&rdquo;</span>\n                </>\n              )}\n\n              {(selectedCollection !== 'all' || selectedTag || searchQuery) && (\n                <button\n                  type=\"button\"\n                  className=\"text-muted-foreground hover:text-foreground ml-2 min-h-6 text-xs underline underline-offset-2\"\n                  onClick={resetFilters}\n                >\n                  Clear filters\n                </button>\n              )}\n            </div>\n\n            <div className=\"text-muted-foreground text-xs tabular-nums\">\n              Showing <span className=\"text-foreground font-semibold\">{filteredBookmarks.length}</span> of{' '}\n              {bookmarks.length} items\n            </div>\n          </div>\n\n          {/* Bookmarks Grid (2 or 3-column responsive) */}\n          {filteredBookmarks.length > 0 ? (\n            <div className=\"grid grid-cols-1 gap-4 sm:gap-5 md:grid-cols-2 xl:grid-cols-3\">\n              {filteredBookmarks.map((item) => (\n                <Card\n                  key={item.id}\n                  className=\"group border-border/80 bg-card hover:border-border relative flex flex-col overflow-hidden rounded-xl border p-0 shadow-xs transition-colors duration-200 hover:shadow-md\"\n                >\n                  {/* Thumbnail Visual Preview */}\n                  <div className=\"bg-muted/40 border-border/60 relative aspect-[16/10] w-full overflow-hidden border-b\">\n                    <img\n                      src={item.previewImage}\n                      alt={item.title}\n                      loading=\"lazy\"\n                      className=\"size-full object-cover transition-transform duration-300 group-hover:scale-105\"\n                    />\n\n                    {/* Gradient overlay */}\n                    <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-black/20 opacity-40 transition-opacity group-hover:opacity-60\" />\n\n                    {/* Domain Badge Top Left */}\n                    <div className=\"absolute top-2.5 left-2.5 flex items-center gap-1.5\">\n                      <div className=\"bg-background text-foreground border-border/80 flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium shadow-xs\">\n                        <Globe className=\"text-muted-foreground size-3\" />\n                        <span className=\"font-mono text-xs\">{item.domain}</span>\n                      </div>\n                    </div>\n\n                    {/* Favorite Toggle Star Button Top Right */}\n                    <button\n                      type=\"button\"\n                      className={cn(\n                        'bg-background border-border/80 focus-visible:ring-ring absolute top-2.5 right-2.5 grid size-7 place-items-center rounded-full border shadow-xs transition-transform duration-150 hover:scale-105 focus-visible:ring-2 focus-visible:outline-none active:scale-90',\n                        item.isFavorite ? 'text-warning' : 'text-muted-foreground hover:text-warning',\n                      )}\n                      title={item.isFavorite ? 'Remove from favorites' : 'Add to favorites'}\n                      aria-label={item.isFavorite ? 'Remove from favorites' : 'Add to favorites'}\n                      onClick={(e) => toggleFavorite(item, e)}\n                    >\n                      <Star className={cn('size-4', item.isFavorite ? 'fill-warning text-warning' : '')} />\n                    </button>\n                  </div>\n\n                  {/* Card Content Body */}\n                  <CardContent className=\"flex flex-1 flex-col justify-between gap-3 p-4\">\n                    <div className=\"space-y-2\">\n                      <div className=\"flex items-start justify-between gap-2\">\n                        <a\n                          href={item.url}\n                          target=\"_blank\"\n                          rel=\"noreferrer noopener\"\n                          className=\"group-hover:text-primary line-clamp-1 min-h-6 py-0.5 text-sm font-semibold tracking-tight transition-colors hover:underline\"\n                        >\n                          {item.title}\n                        </a>\n                      </div>\n\n                      <p className=\"text-muted-foreground line-clamp-2 text-xs leading-relaxed\">{item.excerpt}</p>\n                    </div>\n\n                    {/* Tag Pills */}\n                    <div className=\"flex flex-wrap items-center gap-1.5\">\n                      {item.tags.map((t) => (\n                        <button\n                          key={t}\n                          type=\"button\"\n                          className={cn(\n                            'min-h-6 rounded-md px-2 py-0.5 text-xs font-medium transition-[colors,transform] duration-150 active:scale-95',\n                            selectedTag === t\n                              ? 'bg-primary text-primary-foreground font-semibold'\n                              : 'bg-muted/70 text-muted-foreground hover:bg-muted hover:text-foreground',\n                          )}\n                          onClick={() => toggleTag(t)}\n                        >\n                          {t}\n                        </button>\n                      ))}\n                    </div>\n\n                    {/* Card Footer Actions Row */}\n                    <div className=\"border-border/60 flex items-center justify-between border-t pt-3 text-xs\">\n                      <span className=\"text-muted-foreground font-mono text-xs tabular-nums\">{item.savedAt}</span>\n\n                      <div className=\"flex items-center gap-1.5\">\n                        <Button\n                          variant=\"outline\"\n                          size=\"icon-xs\"\n                          className=\"size-7 transition-transform duration-150 active:scale-95\"\n                          title={copiedId === item.id ? 'Copied URL!' : 'Copy URL'}\n                          aria-label={copiedId === item.id ? 'Copied URL' : 'Copy URL'}\n                          onClick={(e) => copyUrl(item, e)}\n                        >\n                          {copiedId === item.id ? (\n                            <Check className=\"text-primary size-3.5\" />\n                          ) : (\n                            <Copy className=\"size-3.5\" />\n                          )}\n                        </Button>\n\n                        <Button\n                          variant=\"secondary\"\n                          size=\"sm\"\n                          className=\"h-7 gap-1 px-2.5 text-xs font-medium active:scale-[0.98]\"\n                          asChild\n                        >\n                          <a href={item.url} target=\"_blank\" rel=\"noreferrer noopener\">\n                            <span>Open</span>\n                            <ExternalLink className=\"size-3\" />\n                          </a>\n                        </Button>\n                      </div>\n                    </div>\n                  </CardContent>\n                </Card>\n              ))}\n            </div>\n          ) : (\n            /* Empty State */\n            <div className=\"bg-card text-card-foreground border-border/80 flex flex-col items-center justify-center rounded-2xl border p-12 text-center shadow-xs\">\n              <div className=\"bg-muted/70 text-muted-foreground mb-3 grid size-12 place-items-center rounded-xl\">\n                <BookOpen className=\"size-6\" />\n              </div>\n              <h2 className=\"text-base font-semibold\">No bookmarks found</h2>\n              <p className=\"text-muted-foreground mt-1 max-w-sm text-xs\">\n                No engineering resources match your active collection, tag filter, or search query. Try clearing filters\n                or add a new bookmark.\n              </p>\n              <div className=\"mt-4 flex items-center gap-2\">\n                <Button variant=\"outline\" size=\"sm\" className=\"text-xs\" onClick={resetFilters}>\n                  Clear All Filters\n                </Button>\n                <Button size=\"sm\" className=\"text-xs\" onClick={() => setIsAddModalOpen(true)}>\n                  <Plus className=\"size-3.5\" />\n                  Add Bookmark\n                </Button>\n              </div>\n            </div>\n          )}\n        </main>\n      </div>\n\n      {/* Add Bookmark Modal Dialog */}\n      <Dialog open={isAddModalOpen} onOpenChange={setIsAddModalOpen}>\n        <DialogContent className=\"sm:max-w-md\">\n          <DialogHeader className=\"text-left\">\n            <div className=\"flex items-center gap-2\">\n              <div className=\"bg-primary/10 text-primary grid size-7 place-items-center rounded-lg\">\n                <Bookmark className=\"size-4\" />\n              </div>\n              <DialogTitle className=\"text-base font-bold\">Add New Bookmark</DialogTitle>\n            </div>\n            <DialogDescription className=\"text-xs\">\n              Save a web resource or engineering tool with automated metadata fetching.\n            </DialogDescription>\n          </DialogHeader>\n\n          <form className=\"space-y-4 pt-1\" onSubmit={handleSaveBookmark}>\n            {/* URL Input with Auto-Fetch Button */}\n            <div className=\"space-y-1.5\">\n              <label className=\"text-foreground text-xs font-medium\">Website URL</label>\n              <div className=\"flex items-center gap-2\">\n                <Input\n                  value={formUrl}\n                  onChange={(e) => setFormUrl(e.target.value)}\n                  type=\"url\"\n                  placeholder=\"https://astro.build or https://linear.app\"\n                  className=\"h-9 text-xs\"\n                  required\n                />\n                <Button\n                  type=\"button\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  className=\"h-9 shrink-0 gap-1 text-xs font-medium\"\n                  title=\"Fetch metadata from URL\"\n                  onClick={autoFetchMetadata}\n                >\n                  <Globe className=\"text-primary size-3.5\" />\n                  <span>Fetch</span>\n                </Button>\n              </div>\n            </div>\n\n            {/* Quick Samples Preset Bar */}\n            <div className=\"flex flex-wrap items-center gap-1.5 text-xs\">\n              <span className=\"text-muted-foreground text-xs\">Try sample:</span>\n              <button\n                type=\"button\"\n                className=\"border-border bg-muted/60 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 rounded-md border px-2 py-0.5 text-xs transition-colors\"\n                onClick={() => {\n                  setFormUrl('https://astro.build')\n                  setTimeout(autoFetchMetadata, 50)\n                }}\n              >\n                Astro SSG\n              </button>\n              <button\n                type=\"button\"\n                className=\"border-border bg-muted/60 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 rounded-md border px-2 py-0.5 text-xs transition-colors\"\n                onClick={() => {\n                  setFormUrl('https://linear.app/method')\n                  setTimeout(autoFetchMetadata, 50)\n                }}\n              >\n                Linear Method\n              </button>\n              <button\n                type=\"button\"\n                className=\"border-border bg-muted/60 hover:bg-muted text-muted-foreground hover:text-foreground min-h-6 rounded-md border px-2 py-0.5 text-xs transition-colors\"\n                onClick={() => {\n                  setFormUrl('https://github.com/features')\n                  setTimeout(autoFetchMetadata, 50)\n                }}\n              >\n                GitHub Guides\n              </button>\n            </div>\n\n            <Separator className=\"bg-border/60\" />\n\n            {/* Title Input */}\n            <div className=\"space-y-1.5\">\n              <label className=\"text-foreground text-xs font-medium\">Bookmark Title</label>\n              <Input\n                value={formTitle}\n                onChange={(e) => setFormTitle(e.target.value)}\n                type=\"text\"\n                placeholder=\"e.g. Astro 4.0 Server Islands & SSG Docs\"\n                className=\"h-9 text-xs\"\n                required\n              />\n            </div>\n\n            {/* Excerpt Description */}\n            <div className=\"space-y-1.5\">\n              <label className=\"text-foreground text-xs font-medium\">Excerpt & Notes</label>\n              <Input\n                value={formExcerpt}\n                onChange={(e) => setFormExcerpt(e.target.value)}\n                type=\"text\"\n                placeholder=\"Brief description or takeaway notes…\"\n                className=\"h-9 text-xs\"\n              />\n            </div>\n\n            {/* Collection Selector */}\n            <div className=\"space-y-1.5\">\n              <label className=\"text-foreground text-xs font-medium\">Collection Folder</label>\n              <Select\n                value={formCollection}\n                onValueChange={(val) =>\n                  setFormCollection(val as 'design-systems' | 'web-perf' | 'typography' | 'ai-tools' | 'security')\n                }\n              >\n                <SelectTrigger className=\"h-9 text-xs\">\n                  <SelectValue placeholder=\"Select collection\" />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"design-systems\">Design Systems</SelectItem>\n                  <SelectItem value=\"web-perf\">Web Performance</SelectItem>\n                  <SelectItem value=\"typography\">Typography & Fonts</SelectItem>\n                  <SelectItem value=\"ai-tools\">AI & LLM Tools</SelectItem>\n                  <SelectItem value=\"security\">Security & Auth</SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n\n            {/* Tags Input */}\n            <div className=\"space-y-1.5\">\n              <label className=\"text-foreground text-xs font-medium\">Tags (comma separated)</label>\n              <Input\n                value={formTags}\n                onChange={(e) => setFormTags(e.target.value)}\n                type=\"text\"\n                placeholder=\"#astro, #vue, #web-perf\"\n                className=\"h-9 text-xs\"\n              />\n            </div>\n\n            {/* Dialog Footer Buttons */}\n            <DialogFooter className=\"gap-2 pt-2 sm:gap-0\">\n              <DialogClose asChild>\n                <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"text-xs\">\n                  Cancel\n                </Button>\n              </DialogClose>\n              <Button type=\"submit\" size=\"sm\" className=\"text-xs font-medium\">\n                <Plus className=\"size-3.5\" />\n                Save Bookmark\n              </Button>\n            </DialogFooter>\n          </form>\n        </DialogContent>\n      </Dialog>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/BookmarkManagerGrid.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/dialog.json",
    "https://uipkge.dev/r/react/input.json",
    "https://uipkge.dev/r/react/select.json",
    "https://uipkge.dev/r/react/separator.json"
  ],
  "description": "Raindrop style visual web bookmarks and engineering resources manager with collection folders, tag cloud filtering, real-time search, favorite toggles, copy link, and add bookmark modal.",
  "categories": [
    "productivity",
    "app",
    "dashboard",
    "collaboration"
  ]
}