{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "file-dropzone",
  "title": "File Dropzone",
  "description": "Files drop into a knowledge base list and an upload toast confirms.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/file-dropzone/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadGeistMono } from \"@remotion/google-fonts/GeistMono\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  Img,\n  interpolate,\n  spring,\n  staticFile,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport type { GradientParams } from \"@/lib/remocn/gsh/gradient-engine\";\nimport { GshGround } from \"@/lib/remocn/gsh/gsh-ground\";\nimport type { GshPresetId } from \"@/lib/remocn/gsh/presets\";\n\nconst geist = loadGeist(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\nconst geistMono = loadGeistMono(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\nexport type DropFileType = \"pdf\" | \"png\" | \"csv\" | \"doc\" | \"zip\";\n\nexport interface DropFile {\n  name: string;\n  /** Size in bytes. */\n  size: number;\n  /** Inferred from the extension when omitted. */\n  type?: DropFileType;\n}\n\nexport interface FileDropzoneProps {\n  files?: DropFile[];\n  title?: string;\n  /** Where the files go; shown in the window, the rows and the toast. */\n  destination?: string;\n  /** Preview used for image files. */\n  image?: string;\n  accent?: string;\n  speed?: number;\n  theme?: \"light\" | \"dark\";\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n}\n\nconst DEFAULT_FILES: DropFile[] = [\n  { name: \"Q3-board-deck.pdf\", size: 4_200_000 },\n  { name: \"hero-shot@2x.png\", size: 1_800_000 },\n  { name: \"customers-export.csv\", size: 640_000 },\n];\n\nconst BG_ROWS = [\n  [\"Brand guidelines 2026.pdf\", \"PDF\", \"2.1 MB\", \"Oct 21\"],\n  [\"Interview notes, Leo Park.docx\", \"DOC\", \"184 KB\", \"Oct 20\"],\n  [\"Pricing research.csv\", \"CSV\", \"96 KB\", \"Oct 18\"],\n  [\"Onboarding flow v3.png\", \"PNG\", \"3.4 MB\", \"Oct 17\"],\n  [\"Competitive landscape.pdf\", \"PDF\", \"1.2 MB\", \"Oct 14\"],\n  [\"Support tickets Q3.csv\", \"CSV\", \"712 KB\", \"Oct 12\"],\n  [\"Roadmap review.pdf\", \"PDF\", \"880 KB\", \"Oct 9\"],\n];\n\nconst SPR = {\n  heavy: { mass: 1, damping: 26, stiffness: 120 },\n  card: { mass: 0.8, damping: 18, stiffness: 160 },\n  chip: { mass: 0.5, damping: 12, stiffness: 200 },\n  morph: { damping: 200 },\n};\nconst EASE = {\n  enter: Easing.bezier(0.32, 0.72, 0, 1),\n  soft: Easing.bezier(0.22, 1, 0.36, 1),\n  move: Easing.bezier(0.65, 0, 0.35, 1),\n  exit: Easing.bezier(0.55, 0, 1, 0.45),\n  camera: Easing.bezier(0.87, 0, 0.13, 1),\n};\nconst CL = { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\" } as const;\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\nconst tween = (\n  f: number,\n  from: number,\n  len: number,\n  easing: (t: number) => number,\n) => interpolate(f, [from, from + len], [0, 1], { ...CL, easing });\n\nconst BEAT = {\n  window: 0,\n  modal: 6,\n  cursor: 10,\n  hover: 32,\n  drop: 46,\n};\n\nconst HEADER_H = 76;\nconst DZ_OPEN = 176;\nconst DZ_SHUT = 108;\nconst ROW_H = 68;\nconst FOOTER_H = 76;\nconst PAD = 24;\nconst THUMB = { w: 36, h: 44 };\nconst CARD = { w: 112, h: 140 };\nconst ARROW =\n  \"M1 1 L1 18.5 L5.6 14.4 L8.7 21.6 L11.6 20.4 L8.5 13.2 L14.5 13.2 Z\";\n\nfunction withAlpha(color: string, alpha: number) {\n  const hex = color.replace(\"#\", \"\");\n  const full = hex.length === 3 ? hex.replace(/(.)/g, \"$1$1\") : hex;\n  if (!/^[0-9a-fA-F]{6}$/.test(full)) return color;\n  const n = Number.parseInt(full, 16);\n  return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;\n}\n\nfunction fileType(f: DropFile): DropFileType {\n  if (f.type) return f.type;\n  const ext = f.name.split(\".\").pop()?.toLowerCase() ?? \"\";\n  if (ext === \"png\" || ext === \"jpg\" || ext === \"jpeg\") return \"png\";\n  if (ext === \"csv\" || ext === \"xlsx\") return \"csv\";\n  if (ext === \"zip\") return \"zip\";\n  if (ext === \"pdf\") return \"pdf\";\n  return \"doc\";\n}\n\nfunction formatSize(b: number) {\n  if (b >= 1_000_000) return `${(b / 1_000_000).toFixed(1)} MB`;\n  return `${Math.round(b / 1000)} KB`;\n}\n\nconst LIGHT = {\n  win: \"#FCFCFB\",\n  modal: \"#FFFFFF\",\n  text: \"#18181B\",\n  muted: \"#71717A\",\n  faint: \"#A1A1AA\",\n  line: \"rgba(24,24,27,0.08)\",\n  dash: \"rgba(24,24,27,0.2)\",\n  zone: \"#FAFAF9\",\n  track: \"rgba(24,24,27,0.08)\",\n  ghost: \"rgba(24,24,27,0.05)\",\n  disabledBg: \"rgba(24,24,27,0.06)\",\n  disabledText: \"#A1A1AA\",\n  overlay: \"rgba(24,24,27,0.16)\",\n  toastBg: \"#18181B\",\n  toastText: \"#FAFAFA\",\n  toastMuted: \"#A1A1AA\",\n  winShadow:\n    \"0 0 0 1px rgba(24,24,27,0.06), 0 2px 6px rgba(24,24,27,0.04), 0 40px 90px -30px rgba(24,24,40,0.35)\",\n  modalShadow:\n    \"0 0 0 1px rgba(24,24,27,0.06), 0 2px 4px rgba(24,24,27,0.04), 0 28px 64px -16px rgba(24,24,40,0.3)\",\n  btnShadow: \"0 0 0 1px rgba(24,24,27,0.1), 0 1px 2px rgba(24,24,27,0.06)\",\n};\nconst DARK: typeof LIGHT = {\n  win: \"#0E0F11\",\n  modal: \"#17181B\",\n  text: \"#EDEDEF\",\n  muted: \"#8B8D94\",\n  faint: \"#5A5C63\",\n  line: \"rgba(255,255,255,0.07)\",\n  dash: \"rgba(255,255,255,0.18)\",\n  zone: \"rgba(255,255,255,0.02)\",\n  track: \"rgba(255,255,255,0.08)\",\n  ghost: \"rgba(255,255,255,0.05)\",\n  disabledBg: \"rgba(255,255,255,0.06)\",\n  disabledText: \"#5A5C63\",\n  overlay: \"rgba(0,0,0,0.42)\",\n  toastBg: \"#EDEDEF\",\n  toastText: \"#141517\",\n  toastMuted: \"#6B6D74\",\n  winShadow:\n    \"0 0 0 1px rgba(255,255,255,0.08), inset 0 1px 0 rgba(255,255,255,0.05), 0 40px 90px -30px rgba(0,0,0,0.7)\",\n  modalShadow:\n    \"0 0 0 1px rgba(255,255,255,0.09), inset 0 1px 0 rgba(255,255,255,0.06), 0 28px 64px -16px rgba(0,0,0,0.7)\",\n  btnShadow: \"0 0 0 1px rgba(255,255,255,0.1)\",\n};\n\nconst LIGHT_GROUND: Partial<GradientParams> = {\n  colors: [\"#7f9cff\", \"#b9c6ff\", \"#e2e6fb\", \"#3b5bfd\"],\n  background: \"#efeeea\",\n};\n\nfunction FilePreview({\n  type,\n  w,\n  h,\n  r,\n  image,\n}: {\n  type: DropFileType;\n  w: number;\n  h: number;\n  r: number;\n  image: string;\n}) {\n  const box: React.CSSProperties = {\n    position: \"relative\",\n    width: w,\n    height: h,\n    borderRadius: r,\n    overflow: \"hidden\",\n    background: \"#FFFFFF\",\n    flexShrink: 0,\n  };\n  const edge = (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        borderRadius: r,\n        boxShadow: \"inset 0 0 0 1px rgba(0,0,0,0.1)\",\n      }}\n    />\n  );\n  if (type === \"png\") {\n    return (\n      <div style={box}>\n        <Img\n          src={image}\n          style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\" }}\n        />\n        {edge}\n      </div>\n    );\n  }\n  const pad = w * 0.13;\n  if (type === \"csv\") {\n    const rows = 9;\n    return (\n      <div style={box}>\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            right: 0,\n            top: 0,\n            height: h / (rows + 1),\n            background: \"#EEF1F0\",\n          }}\n        />\n        {Array.from({ length: rows }, (_, i) => (\n          <div\n            key={`r${i}`}\n            style={{\n              position: \"absolute\",\n              left: 0,\n              right: 0,\n              top: ((i + 1) * h) / (rows + 1),\n              height: 1,\n              background: \"rgba(0,0,0,0.07)\",\n            }}\n          />\n        ))}\n        {[0.38, 0.7].map((x) => (\n          <div\n            key={`c${x}`}\n            style={{\n              position: \"absolute\",\n              top: 0,\n              bottom: 0,\n              left: w * x,\n              width: 1,\n              background: \"rgba(0,0,0,0.07)\",\n            }}\n          />\n        ))}\n        {Array.from({ length: rows }, (_, i) => (\n          <div\n            key={`v${i}`}\n            style={{\n              position: \"absolute\",\n              left: w * 0.06,\n              top: ((i + 1.38) * h) / (rows + 1),\n              width: w * (0.18 + ((i * 7) % 5) * 0.025),\n              height: Math.max(1.5, h * 0.022),\n              borderRadius: 2,\n              background: \"rgba(0,0,0,0.22)\",\n            }}\n          />\n        ))}\n        {edge}\n      </div>\n    );\n  }\n  const lines = [0.92, 0.86, 0.9, 0.62, 0, 0.88, 0.94, 0.7];\n  return (\n    <div style={box}>\n      <div\n        style={{\n          position: \"absolute\",\n          left: pad,\n          top: pad,\n          width: w * 0.5,\n          height: Math.max(2, h * 0.045),\n          borderRadius: 2,\n          background: \"#1C1C1F\",\n        }}\n      />\n      {lines.map((l, i) =>\n        l === 0 ? null : (\n          <div\n            key={`l${i}`}\n            style={{\n              position: \"absolute\",\n              left: pad,\n              top: pad + h * 0.12 + i * h * 0.055,\n              width: (w - pad * 2) * l,\n              height: Math.max(1.5, h * 0.022),\n              borderRadius: 2,\n              background: \"rgba(0,0,0,0.16)\",\n            }}\n          />\n        ),\n      )}\n      <div\n        style={{\n          position: \"absolute\",\n          left: pad,\n          right: pad,\n          bottom: pad,\n          height: h * 0.2,\n          borderRadius: 2,\n          background: \"#E8ECFF\",\n        }}\n      />\n      {edge}\n    </div>\n  );\n}\n\nfunction Icon({\n  d,\n  size = 20,\n  color,\n  width = 1.5,\n}: {\n  d: string;\n  size?: number;\n  color: string;\n  width?: number;\n}) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke={color}\n      strokeWidth={width}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      style={{ display: \"block\", flexShrink: 0 }}\n    >\n      <path d={d} />\n    </svg>\n  );\n}\n\nconst ICONS = {\n  upload:\n    \"M12 15V4.5M7.5 9L12 4.5 16.5 9M4.5 14.5V18a1.5 1.5 0 001.5 1.5h12a1.5 1.5 0 001.5-1.5v-3.5\",\n  close: \"M6.5 6.5l11 11M17.5 6.5l-11 11\",\n  check: \"M5.5 12.5l4 4 9-9.5\",\n};\n\nexport function FileDropzone({\n  files = DEFAULT_FILES,\n  title = \"Upload files\",\n  destination = \"Research\",\n  image = staticFile(\"reel/reel-2.jpg\"),\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"light\",\n  ground,\n  groundParams,\n}: FileDropzoneProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const tall = height > width;\n  const SW = tall ? 600 : 1280;\n  const SH = tall ? (600 * height) / width : 720;\n  const k = width / SW;\n  const dark = theme === \"dark\";\n  const c = dark ? DARK : LIGHT;\n  const list = files.slice(0, 3);\n  const n = list.length;\n  const sp = (at: number, config: Record<string, number>) =>\n    spring({ frame: frame - at, fps, config });\n\n  const WIN = tall\n    ? { x: 20, y: 96, w: SW - 40, h: SH }\n    : { x: 120, y: 48, w: SW - 240, h: 616 };\n  const MODAL_W = tall ? SW - 40 : 600;\n  const modalLeft = (SW - MODAL_W) / 2;\n  const centerY = tall ? 480 : SH / 2 - 4;\n\n  const rowAt = list.map((_, i) => BEAT.drop + 4 + i * 4);\n  const rowIn = rowAt.map((at) => sp(at, SPR.card));\n  const dur = list.map((f) =>\n    Math.round(Math.min(50, Math.max(24, 20 + (f.size / 1_000_000) * 7))),\n  );\n  const startAt = rowAt.map((at) => at + 4);\n  const doneAt = list.map((_, i) => startAt[i] + dur[i]);\n  const progress = list.map((_, i) =>\n    interpolate(\n      frame,\n      [\n        startAt[i],\n        startAt[i] + dur[i] * 0.42,\n        startAt[i] + dur[i] * 0.62,\n        doneAt[i],\n      ],\n      [0, 0.47, 0.58, 1],\n      { ...CL, easing: EASE.move },\n    ),\n  );\n  const allDone = Math.max(...doneAt, BEAT.drop + 10);\n  const ENABLE = allDone + 2;\n  const TO_BTN = allDone - 12;\n  const CLICK = ENABLE + 10;\n  const CLOSE = CLICK + 4;\n  const TABLE = CLOSE + 4;\n  const TOAST = CLOSE + 12;\n\n  const shut = sp(BEAT.drop + 2, SPR.morph);\n  const dzH = DZ_OPEN + (DZ_SHUT - DZ_OPEN) * shut;\n  const listH = rowIn.reduce((a, b) => a + Math.min(1, b), 0) * ROW_H;\n  const modalH = HEADER_H + dzH + 12 + listH + 8 + FOOTER_H;\n  const modalTop = centerY - modalH / 2;\n  const finalH = HEADER_H + DZ_SHUT + 12 + n * ROW_H + 8 + FOOTER_H;\n  const finalTop = centerY - finalH / 2;\n  const dzTop = modalTop + HEADER_H;\n  const rowsTop = dzTop + dzH + 12;\n  const btnW = 168;\n  const btn = {\n    x: modalLeft + MODAL_W - PAD - btnW / 2,\n    y: finalTop + finalH - FOOTER_H / 2,\n  };\n\n  const start = tall ? { x: 660, y: 1130 } : { x: 1340, y: 800 };\n  const zone = { x: SW / 2 + (tall ? 70 : 120), y: centerY - 76 };\n  const aside = { x: zone.x + (tall ? 110 : 190), y: zone.y + 150 };\n  const legs = [\n    { at: BEAT.cursor, from: start, to: zone, arc: -1 },\n    { at: BEAT.drop + 6, from: zone, to: aside, arc: 1 },\n    { at: TO_BTN, from: aside, to: btn, arc: -1 },\n    { at: CLICK + 12, from: btn, to: { x: btn.x + 46, y: btn.y + 64 }, arc: 1 },\n  ];\n  const cursorAt = (f: number) => {\n    let x = start.x;\n    let y = start.y;\n    for (const leg of legs) {\n      const p = spring({ frame: f - leg.at, fps, config: SPR.heavy });\n      const dx = leg.to.x - leg.from.x;\n      const dy = leg.to.y - leg.from.y;\n      const len = Math.hypot(dx, dy) || 1;\n      const bend = Math.sin(clamp01(p) * Math.PI) * Math.min(70, len * 0.16);\n      x += dx * p + (-dy / len) * bend * leg.arc;\n      y += dy * p + (dx / len) * bend * leg.arc;\n    }\n    return { x, y };\n  };\n  const cur = cursorAt(frame);\n  const held = cursorAt(Math.min(frame, BEAT.drop) - 2);\n  const press = clamp01(sp(CLICK - 2, SPR.chip) - sp(CLICK + 3, SPR.chip));\n\n  const hot = clamp01(sp(BEAT.hover, SPR.card) - sp(BEAT.drop + 2, SPR.card));\n  const winIn = sp(BEAT.window, SPR.heavy);\n  const winOp = tween(frame, BEAT.window, 10, EASE.enter);\n  const modalIn = sp(BEAT.modal, SPR.card);\n  const modalOp = tween(frame, BEAT.modal, 8, EASE.enter);\n  const close = tween(frame, CLOSE, 8, EASE.exit);\n  const dim =\n    tween(frame, BEAT.modal, 12, EASE.soft) *\n    (1 - tween(frame, CLOSE, 12, EASE.soft));\n  const enabled = tween(frame, ENABLE, 6, EASE.enter);\n  const toastIn = sp(TOAST, SPR.card);\n  const toastOp = tween(frame, TOAST, 6, EASE.enter);\n  const cam =\n    1 +\n    0.035 * tween(frame, 8, 54, EASE.camera) -\n    0.035 * tween(frame, CLOSE - 2, 26, EASE.camera);\n  const cursorOp = tween(frame, BEAT.cursor, 4, EASE.enter);\n  const badge =\n    sp(BEAT.cursor + 4, SPR.chip) * (1 - tween(frame, BEAT.drop, 5, EASE.exit));\n  const total = list.reduce((a, f) => a + f.size, 0);\n  const dzLive = hot > 0.5;\n\n  const table = [\n    ...list.map((f, i) => ({\n      cells: [\n        f.name,\n        fileType(f).toUpperCase(),\n        formatSize(f.size),\n        \"Just now\",\n      ],\n      fresh: i,\n    })),\n    ...BG_ROWS.map((cells) => ({ cells, fresh: -1 })),\n  ];\n  const cols = tall\n    ? [\n        { i: 0, w: 0 },\n        { i: 2, w: 110 },\n      ]\n    : [\n        { i: 0, w: 0 },\n        { i: 1, w: 120 },\n        { i: 2, w: 120 },\n        { i: 3, w: 130 },\n      ];\n  const tint = 1 - tween(frame, TABLE + 24, 40, EASE.soft);\n\n  return (\n    <AbsoluteFill\n      style={{\n        fontFamily: geist.fontFamily,\n        WebkitFontSmoothing: \"antialiased\",\n        overflow: \"hidden\",\n      }}\n    >\n      <GshGround\n        preset={ground ?? (dark ? \"earthshine\" : \"paper-crease\")}\n        params={groundParams ?? (dark ? undefined : LIGHT_GROUND)}\n        timeOffset={3}\n        timeScale={0.5}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          width: SW,\n          height: SH,\n          transform: `scale(${k})`,\n          transformOrigin: \"0 0\",\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            transform: `scale(${cam})`,\n            transformOrigin: `${SW / 2}px ${centerY}px`,\n          }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              left: WIN.x,\n              top: WIN.y,\n              width: WIN.w,\n              height: WIN.h,\n              borderRadius: 18,\n              overflow: \"hidden\",\n              background: c.win,\n              boxShadow: c.winShadow,\n              opacity: winOp,\n              transform: `translateY(${(1 - winIn) * 40}px)`,\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                inset: 0,\n                padding: tall ? \"32px 28px\" : \"36px 40px\",\n                filter: `blur(${dim * 2}px)`,\n              }}\n            >\n              <div style={{ display: \"flex\", alignItems: \"flex-end\" }}>\n                <div>\n                  <div style={{ fontSize: 16, color: c.muted }}>Library</div>\n                  <div\n                    style={{\n                      fontSize: 30,\n                      fontWeight: 400,\n                      letterSpacing: \"-0.025em\",\n                      color: c.text,\n                      marginTop: 6,\n                    }}\n                  >\n                    {destination}\n                  </div>\n                </div>\n                <div\n                  style={{\n                    marginLeft: \"auto\",\n                    height: 40,\n                    padding: \"0 16px\",\n                    borderRadius: 10,\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    gap: 8,\n                    fontSize: 16,\n                    color: c.text,\n                    boxShadow: c.btnShadow,\n                  }}\n                >\n                  <Icon d={ICONS.upload} size={18} color={c.text} />\n                  Upload\n                </div>\n              </div>\n              <div style={{ marginTop: 30 }}>\n                <div\n                  style={{\n                    display: \"flex\",\n                    height: 40,\n                    alignItems: \"center\",\n                    fontSize: 16,\n                    color: c.muted,\n                    borderBottom: `1px solid ${c.line}`,\n                  }}\n                >\n                  {cols.map((col) => (\n                    <span\n                      key={col.i}\n                      style={col.w ? { width: col.w } : { flex: 1 }}\n                    >\n                      {[\"Name\", \"Kind\", \"Size\", \"Added\"][col.i]}\n                    </span>\n                  ))}\n                </div>\n                {table.map((r) => {\n                  const fresh = r.fresh >= 0;\n                  const p = fresh ? sp(TABLE + r.fresh * 4, SPR.card) : 1;\n                  const hgt = 54 * Math.min(1, p);\n                  if (fresh && hgt <= 0.01) return null;\n                  return (\n                    <div\n                      key={r.cells[0]}\n                      style={{\n                        height: hgt,\n                        overflow: \"hidden\",\n                        borderBottom: `1px solid ${c.line}`,\n                        background: fresh\n                          ? withAlpha(accent, (dark ? 0.12 : 0.07) * tint)\n                          : \"transparent\",\n                        margin: \"0 -12px\",\n                        padding: \"0 12px\",\n                        borderRadius: 8,\n                      }}\n                    >\n                      <div\n                        style={{\n                          display: \"flex\",\n                          alignItems: \"center\",\n                          height: 54,\n                          fontSize: 17,\n                          color: c.text,\n                          opacity: fresh ? clamp01(p * 1.4 - 0.3) : 1,\n                          transform: `translateY(${(1 - p) * -10}px)`,\n                        }}\n                      >\n                        {cols.map((col) => (\n                          <span\n                            key={col.i}\n                            style={{\n                              ...(col.w ? { width: col.w } : { flex: 1 }),\n                              color: col.i === 0 ? c.text : c.muted,\n                              fontFamily:\n                                col.i === 2 ? geistMono.fontFamily : undefined,\n                              fontSize: col.i === 2 ? 15.5 : 17,\n                              fontVariantNumeric: \"tabular-nums\",\n                              whiteSpace: \"nowrap\",\n                              overflow: \"hidden\",\n                              textOverflow: \"ellipsis\",\n                              paddingRight: col.i === 0 ? 16 : 0,\n                            }}\n                          >\n                            {col.i === 3 && fresh ? (\n                              <span style={{ color: accent }}>\n                                {r.cells[3]}\n                              </span>\n                            ) : (\n                              r.cells[col.i]\n                            )}\n                          </span>\n                        ))}\n                      </div>\n                    </div>\n                  );\n                })}\n              </div>\n            </div>\n            <div\n              style={{\n                position: \"absolute\",\n                inset: 0,\n                background: c.overlay,\n                opacity: dim,\n              }}\n            />\n          </div>\n\n          <div\n            style={{\n              position: \"absolute\",\n              left: modalLeft,\n              top: modalTop,\n              width: MODAL_W,\n              height: modalH,\n              borderRadius: 20,\n              background: c.modal,\n              boxShadow: c.modalShadow,\n              overflow: \"hidden\",\n              opacity: modalOp * (1 - close),\n              transform: `translateY(${(1 - modalIn) * 18 + close * 10}px) scale(${(0.97 + 0.03 * modalIn) * (1 - 0.03 * close)})`,\n            }}\n          >\n            <div\n              style={{\n                height: HEADER_H,\n                display: \"flex\",\n                alignItems: \"center\",\n                padding: `0 ${PAD}px`,\n              }}\n            >\n              <div\n                style={{\n                  fontSize: 21,\n                  letterSpacing: \"-0.015em\",\n                  color: c.text,\n                }}\n              >\n                {title}\n              </div>\n              <div style={{ marginLeft: \"auto\" }}>\n                <Icon d={ICONS.close} size={20} color={c.muted} />\n              </div>\n            </div>\n            <div\n              style={{\n                position: \"relative\",\n                margin: `0 ${PAD}px`,\n                height: dzH,\n                borderRadius: 12,\n                background: dzLive ? withAlpha(accent, 0.06) : c.zone,\n                display: \"flex\",\n                flexDirection: \"column\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                gap: 10,\n              }}\n            >\n              <svg\n                width=\"100%\"\n                height=\"100%\"\n                style={{ position: \"absolute\", inset: 0 }}\n                aria-hidden\n              >\n                <rect\n                  x={0.5}\n                  y={0.5}\n                  width={MODAL_W - PAD * 2 - 1}\n                  height={dzH - 1}\n                  rx={11.5}\n                  fill=\"none\"\n                  stroke={\n                    hot > 0.02 ? withAlpha(accent, 0.3 + 0.7 * hot) : c.dash\n                  }\n                  strokeWidth={1}\n                  strokeDasharray={dzLive ? undefined : \"5 5\"}\n                />\n              </svg>\n              <div\n                style={{\n                  transform: `translateY(${-hot * 4}px)`,\n                }}\n              >\n                <Icon\n                  d={ICONS.upload}\n                  size={26}\n                  color={hot > 0.5 ? accent : c.muted}\n                />\n              </div>\n              <div style={{ fontSize: 17, color: c.text }}>\n                {dzLive ? (\n                  <span style={{ color: accent }}>Release to upload</span>\n                ) : (\n                  <>\n                    Drop files here or{\" \"}\n                    <span style={{ color: accent }}>browse</span>\n                  </>\n                )}\n              </div>\n              <div\n                style={{\n                  fontSize: 16,\n                  color: c.muted,\n                  opacity: 1 - shut,\n                  height: 22 * (1 - shut),\n                  overflow: \"hidden\",\n                }}\n              >\n                PDF, PNG or CSV, up to 50 MB\n              </div>\n            </div>\n            <div\n              style={{\n                position: \"relative\",\n                margin: `12px ${PAD}px 0`,\n                height: listH,\n              }}\n            >\n              {list.map((f, i) => {\n                const p = progress[i];\n                const done = frame >= doneAt[i];\n                const tick = tween(frame, doneAt[i], 8, EASE.enter);\n                const before = rowIn\n                  .slice(0, i)\n                  .reduce((a, b) => a + Math.min(1, b), 0);\n                const fly = sp(BEAT.drop + i * 3, SPR.card);\n                return (\n                  <div\n                    key={f.name}\n                    style={{\n                      position: \"absolute\",\n                      left: 0,\n                      right: 0,\n                      top: before * ROW_H,\n                      height: ROW_H,\n                      display: \"flex\",\n                      alignItems: \"center\",\n                      gap: 14,\n                      borderTop: i > 0 ? `1px solid ${c.line}` : \"none\",\n                      opacity: clamp01(rowIn[i] * 1.6),\n                    }}\n                  >\n                    <div style={{ opacity: clamp01((fly - 0.82) / 0.18) }}>\n                      <FilePreview\n                        type={fileType(f)}\n                        w={THUMB.w}\n                        h={THUMB.h}\n                        r={6}\n                        image={image}\n                      />\n                    </div>\n                    <div style={{ flex: 1, minWidth: 0 }}>\n                      <div\n                        style={{\n                          display: \"flex\",\n                          alignItems: \"baseline\",\n                          fontSize: 17,\n                          color: c.text,\n                        }}\n                      >\n                        <span\n                          style={{\n                            whiteSpace: \"nowrap\",\n                            overflow: \"hidden\",\n                            textOverflow: \"ellipsis\",\n                          }}\n                        >\n                          {f.name}\n                        </span>\n                        <span\n                          style={{\n                            marginLeft: \"auto\",\n                            paddingLeft: 12,\n                            fontFamily: geistMono.fontFamily,\n                            fontSize: 15.5,\n                            color: c.muted,\n                            fontVariantNumeric: \"tabular-nums\",\n                            whiteSpace: \"nowrap\",\n                          }}\n                        >\n                          {done\n                            ? formatSize(f.size)\n                            : `${Math.round(p * 100)}%`}\n                        </span>\n                      </div>\n                      <div\n                        style={{\n                          height: 2,\n                          borderRadius: 1,\n                          background: c.track,\n                          marginTop: 10,\n                          overflow: \"hidden\",\n                          opacity: 1 - tick,\n                        }}\n                      >\n                        <div\n                          style={{\n                            width: `${p * 100}%`,\n                            height: \"100%\",\n                            background: accent,\n                          }}\n                        />\n                      </div>\n                    </div>\n                    <div\n                      style={{\n                        width: 22,\n                        display: \"flex\",\n                        justifyContent: \"flex-end\",\n                      }}\n                    >\n                      <svg\n                        width={22}\n                        height={22}\n                        viewBox=\"0 0 24 24\"\n                        style={{ opacity: done ? 1 : 0 }}\n                        aria-hidden\n                      >\n                        <path\n                          d={ICONS.check}\n                          fill=\"none\"\n                          stroke={accent}\n                          strokeWidth={1.75}\n                          strokeLinecap=\"round\"\n                          strokeLinejoin=\"round\"\n                          strokeDasharray={20}\n                          strokeDashoffset={20 * (1 - tick)}\n                        />\n                      </svg>\n                    </div>\n                  </div>\n                );\n              })}\n            </div>\n            <div\n              style={{\n                position: \"absolute\",\n                left: 0,\n                right: 0,\n                bottom: 0,\n                height: FOOTER_H,\n                borderTop: `1px solid ${c.line}`,\n                display: \"flex\",\n                alignItems: \"center\",\n                gap: 10,\n                padding: `0 ${PAD}px`,\n              }}\n            >\n              <span\n                style={{\n                  fontFamily: geistMono.fontFamily,\n                  fontSize: 15.5,\n                  color: c.muted,\n                  fontVariantNumeric: \"tabular-nums\",\n                  opacity: clamp01(rowIn[0] ?? 0),\n                }}\n              >\n                {n} files · {formatSize(total)}\n              </span>\n              <div\n                style={{\n                  marginLeft: \"auto\",\n                  height: 42,\n                  padding: \"0 18px\",\n                  borderRadius: 10,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  fontSize: 16,\n                  color: c.text,\n                  boxShadow: c.btnShadow,\n                }}\n              >\n                Cancel\n              </div>\n              <div\n                style={{\n                  position: \"relative\",\n                  width: btnW,\n                  height: 42,\n                  borderRadius: 10,\n                  overflow: \"hidden\",\n                  background: c.disabledBg,\n                  transform: `scale(${1 - 0.04 * press})`,\n                }}\n              >\n                <div\n                  style={{\n                    position: \"absolute\",\n                    inset: 0,\n                    background: accent,\n                    opacity: enabled,\n                    boxShadow: \"inset 0 1px 0 rgba(255,255,255,0.16)\",\n                  }}\n                />\n                <div\n                  style={{\n                    position: \"absolute\",\n                    inset: 0,\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    justifyContent: \"center\",\n                    fontSize: 16,\n                    color: enabled > 0.5 ? \"#FFFFFF\" : c.disabledText,\n                  }}\n                >\n                  Upload {n} files\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <div\n            style={{\n              position: \"absolute\",\n              left: SW / 2 - (tall ? 250 : 230),\n              top: tall ? SH - 150 : WIN.y + WIN.h - 92,\n              width: tall ? 500 : 460,\n              height: 60,\n              borderRadius: 14,\n              background: c.toastBg,\n              boxShadow: dark\n                ? \"0 18px 40px -12px rgba(0,0,0,0.6)\"\n                : \"0 2px 6px rgba(24,24,27,0.1), 0 18px 40px -12px rgba(24,24,40,0.45)\",\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: 12,\n              padding: \"0 18px\",\n              boxSizing: \"border-box\",\n              opacity: toastOp,\n              transform: `translateY(${(1 - toastIn) * 28}px) scale(${0.97 + 0.03 * toastIn})`,\n            }}\n          >\n            <Icon d={ICONS.check} size={20} color={c.toastText} width={1.75} />\n            <span style={{ fontSize: 17, color: c.toastText }}>\n              {n} files added to {destination}\n            </span>\n            <span\n              style={{ marginLeft: \"auto\", fontSize: 16, color: c.toastMuted }}\n            >\n              View\n            </span>\n          </div>\n\n          {list.map((f, i) => {\n            const kk = i - (n - 1) / 2;\n            const appear = sp(BEAT.cursor + i * 2, SPR.card);\n            const rest = {\n              x: held.x + 44 + kk * 20,\n              y: held.y + 62 + Math.abs(kk) * 6,\n            };\n            const target = {\n              x: modalLeft + PAD + THUMB.w / 2,\n              y: rowsTop + i * ROW_H + ROW_H / 2,\n            };\n            const fly = sp(BEAT.drop + i * 3, SPR.card);\n            const x = rest.x + (target.x - rest.x) * fly;\n            const y = rest.y + (target.y - rest.y) * fly;\n            const s = (1 - fly * (1 - THUMB.w / CARD.w)) * (0.9 + 0.1 * appear);\n            const rot = kk * 8 * (1 - fly);\n            const o = clamp01(appear * 2) * (1 - clamp01((fly - 0.82) / 0.18));\n            if (o <= 0.001) return null;\n            return (\n              <div\n                key={f.name}\n                style={{\n                  position: \"absolute\",\n                  left: x - CARD.w / 2,\n                  top: y - CARD.h / 2,\n                  opacity: o,\n                  transform: `rotate(${rot}deg) scale(${s})`,\n                  borderRadius: 10,\n                  boxShadow: `0 1px 2px rgba(0,0,0,0.12), 0 ${16 * (1 - fly) + 4}px ${30 * (1 - fly) + 8}px -6px rgba(10,12,30,${0.4 * (1 - fly) + 0.1})`,\n                  zIndex: i === 1 ? 3 : 2,\n                }}\n              >\n                <FilePreview\n                  type={fileType(f)}\n                  w={CARD.w}\n                  h={CARD.h}\n                  r={10}\n                  image={image}\n                />\n              </div>\n            );\n          })}\n          {badge > 0.01 ? (\n            <div\n              style={{\n                position: \"absolute\",\n                left: cur.x + 92,\n                top: cur.y + 4,\n                width: 28,\n                height: 28,\n                borderRadius: 14,\n                background: accent,\n                color: \"#FFFFFF\",\n                fontSize: 16,\n                fontFamily: geistMono.fontFamily,\n                display: \"flex\",\n                alignItems: \"center\",\n                justifyContent: \"center\",\n                boxShadow: \"0 4px 10px -2px rgba(10,12,30,0.35)\",\n                transform: `scale(${badge})`,\n                zIndex: 4,\n              }}\n            >\n              {n}\n            </div>\n          ) : null}\n          <svg\n            width={26}\n            height={26}\n            viewBox=\"0 0 24 24\"\n            style={{\n              position: \"absolute\",\n              left: cur.x,\n              top: cur.y,\n              overflow: \"visible\",\n              opacity: cursorOp,\n              transform: `scale(${1 - 0.04 * press})`,\n              transformOrigin: \"2px 2px\",\n              filter: \"drop-shadow(0 2px 3px rgba(0,0,0,0.3))\",\n              zIndex: 5,\n            }}\n            aria-hidden\n          >\n            <path\n              d={ARROW}\n              fill=\"#FFFFFF\"\n              stroke=\"#111111\"\n              strokeWidth={1.1}\n              strokeLinejoin=\"round\"\n            />\n          </svg>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/file-dropzone.tsx"
    }
  ],
  "type": "registry:component"
}
