{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "results-gallery-01",
  "type": "registry:block",
  "title": "Generated Results Gallery",
  "description": "A result gallery with output previews and download actions for a media creation workspace.",
  "author": "UIBlocks contributors",
  "registryDependencies": [],
  "dependencies": [
    "lucide-react@0.577.0"
  ],
  "files": [
    {
      "path": "licenses/results-gallery-01/LICENSE.txt",
      "type": "registry:file",
      "target": "@components/uiblocks/results-gallery-01/LICENSE.txt",
      "content": "UIBlocks original block: Copyright (c) 2026 UIBlocks contributors. Licensed under the MIT License below.\n\nIncludes source from Blocks.so and its bundled primitives.\nSource: https://github.com/ephraimduncan/blocks\nRevision: 3f759d5171c2391253ec4c772e9e5b3db2bc7c7f\n\nMIT License\n\nCopyright (c) 2025 Ephraim Duncan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
    },
    {
      "path": "content/components/workspace/results-gallery-01.tsx",
      "type": "registry:component",
      "target": "@components/uiblocks/results-gallery-01/results-gallery-01.tsx",
      "content": "\"use client\";\n\nimport {\n  ArrowDownToLine,\n  Check,\n  FileImage,\n  ImagePlus,\n  Maximize2,\n  X,\n} from \"lucide-react\";\nimport { useEffect, useId, useRef, useState } from \"react\";\n\nexport type DemoResult = {\n  id: string;\n  name: string;\n  format: string;\n  detail: string;\n  tone?: \"violet\" | \"mint\" | \"peach\";\n};\nexport type ResultsGallery01Props = {\n  title?: string;\n  results?: DemoResult[];\n  searchQuery?: string;\n};\nconst sampleResults: DemoResult[] = [\n  {\n    id: \"result-1\",\n    name: \"Autumn campaign\",\n    format: \"MP4\",\n    detail: \"Sample output · SVG preview\",\n    tone: \"violet\",\n  },\n  {\n    id: \"result-2\",\n    name: \"Product walkthrough\",\n    format: \"WebM\",\n    detail: \"Sample output · SVG preview\",\n    tone: \"mint\",\n  },\n];\n\nfunction escapeXml(value: string) {\n  return value.replace(\n    /[<>&\"']/g,\n    (character) =>\n      ({\n        \"<\": \"&lt;\",\n        \">\": \"&gt;\",\n        \"&\": \"&amp;\",\n        '\"': \"&quot;\",\n        \"'\": \"&apos;\",\n      })[character] ?? character\n  );\n}\n\n/** Downloadable SVG preview cards. No video conversion is implied. */\nexport default function ResultsGallery01({\n  title = \"Ready for your next chapter.\",\n  results = sampleResults,\n  searchQuery = \"\",\n}: ResultsGallery01Props = {}) {\n  const [selected, setSelected] = useState<DemoResult | null>(null);\n  const [downloaded, setDownloaded] = useState<string | null>(null);\n  const id = useId();\n  const dialogRef = useRef<HTMLDialogElement>(null);\n  useEffect(() => {\n    const dialog = dialogRef.current;\n    if (!dialog) return;\n    if (selected && !dialog.open) dialog.showModal();\n    else if (!selected && dialog.open) dialog.close();\n  }, [selected]);\n  const visible = results.filter((result) =>\n    result.name.toLowerCase().includes(searchQuery.toLowerCase())\n  );\n\n  function download(result: DemoResult) {\n    const colors = { violet: \"#ede9fe\", mint: \"#d1fae5\", peach: \"#ffedd5\" };\n    const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"750\" viewBox=\"0 0 1200 750\"><rect width=\"1200\" height=\"750\" fill=\"${colors[result.tone ?? \"violet\"]}\"/><rect x=\"120\" y=\"100\" width=\"960\" height=\"550\" rx=\"32\" fill=\"#fff\"/><text x=\"180\" y=\"200\" font-family=\"system-ui,sans-serif\" font-size=\"24\" fill=\"#7c3aed\">FRAME / WORKFLOW DEMO</text><text x=\"180\" y=\"310\" font-family=\"system-ui,sans-serif\" font-size=\"38\" font-weight=\"600\" fill=\"#18181b\">${escapeXml(result.name.slice(0, 45))}</text><text x=\"180\" y=\"375\" font-family=\"system-ui,sans-serif\" font-size=\"20\" fill=\"#71717a\">Selected output: ${escapeXml(result.format)}</text><text x=\"180\" y=\"520\" font-family=\"system-ui,sans-serif\" font-size=\"18\" fill=\"#71717a\">This is an SVG demo preview. No video was uploaded or converted.</text></svg>`;\n    const url = URL.createObjectURL(new Blob([svg], { type: \"image/svg+xml\" }));\n    const anchor = document.createElement(\"a\");\n    anchor.href = url;\n    anchor.download = `${result.name.replace(/[^a-z0-9-]/gi, \"-\").toLowerCase()}-demo.svg`;\n    anchor.click();\n    window.setTimeout(() => URL.revokeObjectURL(url), 1000);\n    setDownloaded(result.id);\n  }\n\n  function artwork(result: DemoResult, expanded = false) {\n    const tones = {\n      violet: \"bg-violet-100 dark:bg-violet-950/60\",\n      mint: \"bg-emerald-100 dark:bg-emerald-950/60\",\n      peach: \"bg-orange-100 dark:bg-orange-950/60\",\n    };\n    return (\n      <div\n        className={`relative flex aspect-[16/10] items-center justify-center overflow-hidden ${tones[result.tone ?? \"violet\"]} ${expanded ? \"rounded-xl\" : \"\"}`}\n      >\n        <div\n          className=\"absolute inset-0 opacity-30\"\n          style={{\n            backgroundImage:\n              \"radial-gradient(currentColor 0.7px, transparent 0.7px)\",\n            backgroundSize: \"12px 12px\",\n            color: \"#a1a1aa\",\n          }}\n        />\n        <div className=\"relative w-[68%] -rotate-6 rounded-lg border border-white/80 bg-white p-[8%] shadow-lg shadow-zinc-800/5 dark:border-zinc-700 dark:bg-zinc-900\">\n          <span className=\"text-[8px] font-medium uppercase tracking-[0.18em] text-zinc-400\">\n            FRAME STUDIO\n          </span>\n          <div className=\"mt-3 space-y-1.5\">\n            <div className=\"h-2.5 w-4/5 rounded bg-zinc-800 dark:bg-zinc-200\" />\n            <div className=\"h-2.5 w-3/5 rounded bg-zinc-800 dark:bg-zinc-200\" />\n          </div>\n          <div className=\"mt-4 flex gap-1\">\n            <span className=\"h-1 w-3 rounded bg-violet-400\" />\n            <span className=\"h-1 w-7 rounded bg-zinc-200 dark:bg-zinc-700\" />\n          </div>\n        </div>\n        <span className=\"absolute right-2.5 bottom-2 rounded-md bg-white/80 px-1.5 py-0.5 text-[8px] font-medium text-zinc-600 backdrop-blur dark:bg-zinc-900/80 dark:text-zinc-300\">\n          SVG demo\n        </span>\n      </div>\n    );\n  }\n\n  return (\n    <section\n      aria-labelledby={id}\n      className=\"w-full rounded-2xl border border-zinc-200 bg-white p-5 text-zinc-950 shadow-sm shadow-zinc-950/[0.02] sm:p-6 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50\"\n    >\n      <div className=\"flex items-center justify-between gap-3\">\n        <div>\n          <h2 className=\"text-sm font-semibold\" id={id}>\n            {title}\n          </h2>\n          <p className=\"mt-1 text-[11px] text-zinc-400\">\n            Preview cards you can actually download.\n          </p>\n        </div>\n        <span className=\"grid size-8 place-items-center rounded-lg border border-zinc-100 text-zinc-400 dark:border-zinc-800\">\n          <FileImage aria-hidden=\"true\" className=\"size-4\" />\n        </span>\n      </div>\n      {visible.length ? (\n        <div className=\"mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n          {visible.map((result) => (\n            <article\n              className=\"overflow-hidden rounded-xl border border-zinc-200 dark:border-zinc-800\"\n              key={result.id}\n            >\n              <button\n                aria-label={`Preview ${result.name}`}\n                className=\"group relative block w-full text-left focus-visible:outline-2 focus-visible:outline-violet-500\"\n                onClick={() => setSelected(result)}\n                type=\"button\"\n              >\n                {artwork(result)}\n                <span className=\"absolute top-2.5 right-2.5 grid size-7 place-items-center rounded-lg bg-white/80 text-zinc-500 opacity-100 transition-opacity sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-visible:opacity-100 dark:bg-zinc-900/80\">\n                  <Maximize2 aria-hidden=\"true\" className=\"size-3\" />\n                </span>\n              </button>\n              <div className=\"flex items-center gap-2 p-3\">\n                <div className=\"min-w-0 flex-1\">\n                  <h3 className=\"truncate text-xs font-medium\">\n                    {result.name}\n                  </h3>\n                  <p className=\"mt-1 text-[9px] text-zinc-400\">\n                    {result.detail}\n                  </p>\n                </div>\n                <button\n                  aria-label={`Download SVG demo for ${result.name}`}\n                  className=\"grid size-8 shrink-0 place-items-center rounded-lg border border-zinc-200 text-zinc-500 transition-colors hover:border-violet-300 hover:bg-violet-50 hover:text-violet-600 focus-visible:outline-2 focus-visible:outline-violet-500 dark:border-zinc-700 dark:hover:bg-violet-950/40 dark:hover:text-violet-300\"\n                  onClick={() => download(result)}\n                  type=\"button\"\n                >\n                  {downloaded === result.id ? (\n                    <Check\n                      aria-hidden=\"true\"\n                      className=\"size-3.5 text-emerald-500\"\n                    />\n                  ) : (\n                    <ArrowDownToLine aria-hidden=\"true\" className=\"size-3.5\" />\n                  )}\n                </button>\n              </div>\n            </article>\n          ))}\n        </div>\n      ) : (\n        <div className=\"flex min-h-48 flex-col items-center justify-center rounded-xl border border-dashed border-zinc-200 p-6 text-center dark:border-zinc-800\">\n          <ImagePlus\n            aria-hidden=\"true\"\n            className=\"size-7 text-zinc-300 dark:text-zinc-600\"\n          />\n          <p className=\"mt-3 text-xs font-medium\">\n            {searchQuery ? \"No matching results\" : \"Room for your next idea.\"}\n          </p>\n          <p className=\"mt-1 max-w-56 text-[11px] leading-5 text-zinc-400\">\n            {searchQuery\n              ? \"Try another file name.\"\n              : \"Complete a demo workflow to see your preview cards here.\"}\n          </p>\n        </div>\n      )}\n      {downloaded && (\n        <p\n          className=\"mt-3 text-[10px] text-emerald-600 dark:text-emerald-400\"\n          role=\"status\"\n        >\n          SVG demo preview downloaded. Your source video was not converted.\n        </p>\n      )}\n      <dialog\n        aria-label={\n          selected ? `${selected.name} demo preview` : \"Result preview\"\n        }\n        className=\"fixed inset-0 m-0 h-dvh max-h-none w-screen max-w-none items-center justify-center bg-transparent p-5 text-zinc-950 open:flex backdrop:bg-zinc-950/50 backdrop:backdrop-blur-sm dark:text-zinc-50\"\n        onCancel={(event) => {\n          event.preventDefault();\n          setSelected(null);\n        }}\n        onClick={(event) => {\n          if (event.target === event.currentTarget) setSelected(null);\n        }}\n        onClose={() => setSelected(null)}\n        ref={dialogRef}\n      >\n        {selected && (\n          <div className=\"relative my-auto w-full max-w-xl rounded-2xl bg-white p-5 shadow-2xl dark:bg-zinc-950\">\n            <div className=\"mb-4 flex items-center justify-between gap-4\">\n              <h3 className=\"truncate text-sm font-medium\">{selected.name}</h3>\n              <button\n                aria-label=\"Close preview\"\n                autoFocus\n                className=\"grid size-8 shrink-0 place-items-center rounded-lg text-zinc-500 hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n                onClick={() => setSelected(null)}\n                type=\"button\"\n              >\n                <X aria-hidden=\"true\" className=\"size-4\" />\n              </button>\n            </div>\n            {artwork(selected, true)}\n            <p className=\"mt-4 text-xs leading-5 text-zinc-500\">\n              This is an SVG demo card. Your videos stay local and are not\n              converted.\n            </p>\n            <button\n              className=\"mt-4 inline-flex min-h-10 items-center gap-2 rounded-lg bg-violet-600 px-4 text-xs font-medium text-white hover:bg-violet-700\"\n              onClick={() => download(selected)}\n              type=\"button\"\n            >\n              <ArrowDownToLine aria-hidden=\"true\" className=\"size-3.5\" />{\" \"}\n              Download SVG demo\n            </button>\n          </div>\n        )}\n      </dialog>\n    </section>\n  );\n}\n"
    }
  ],
  "categories": [
    "workspace",
    "application",
    "clean-saas"
  ],
  "docs": "Import the default component from @components/uiblocks/results-gallery-01/results-gallery-01.tsx. React 19, Tailwind CSS 4, and the standard shadcn CSS theme variables are required. This block is framework-independent React and works in TanStack Start, Vite, and Next.js projects. Connect links or navigation callbacks to your host application. Demo avatars use public image URLs; replace them with your own assets. Demo handlers must be connected to your product. See https://uiblocks.org/workspace/results-gallery-01 for content guidelines and source attribution.",
  "meta": {
    "kind": "block",
    "version": "0.1.0",
    "source": {
      "name": "UIBlocks",
      "url": "https://uiblocks.org",
      "license": "MIT",
      "author": "UIBlocks contributors",
      "revision": "uiblocks-v0.1.0"
    },
    "license": "MIT",
    "style": "clean-saas",
    "pageType": "application",
    "featured": true,
    "tags": [
      "results",
      "gallery",
      "output",
      "download",
      "media",
      "结果",
      "作品",
      "图库",
      "下载",
      "媒体"
    ],
    "contentGuidelines": [
      "Give each output a useful title, format, and downloadable file.",
      "Keep thumbnail aspect ratios consistent and provide an empty state."
    ],
    "recommendedWith": [
      "upload-workflow-01",
      "task-queue-01"
    ],
    "entry": "@components/uiblocks/results-gallery-01/results-gallery-01.tsx",
    "exportName": "default",
    "frameworkIndependent": true,
    "frameworkDependencies": [],
    "compatibleHosts": [
      "react",
      "vite",
      "tanstack-start",
      "nextjs"
    ],
    "requirements": {
      "react": ">=19",
      "tailwind": ">=4"
    },
    "previewUrl": "https://uiblocks.org/preview/results-gallery-01"
  }
}
