{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "device-morph",
  "title": "Device Morph",
  "description": "A design canvas switches a frame between desktop, tablet and phone layouts.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/device-morph/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  Img,\n  interpolate,\n  interpolateColors,\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 { fontFamily } = loadFont(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: mono } = loadMono(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\nconst REMOCN_R_PATH =\n  \"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z\";\n\nexport interface DeviceStop {\n  label: string;\n  width: number;\n  height: number;\n}\n\nexport interface DeviceMorphProps {\n  /** Your own responsive UI; it is laid out at the live viewport width every frame. */\n  children?: React.ReactNode;\n  /** A screenshot to show instead of the built-in dashboard (no reflow). */\n  src?: string;\n  /** Exactly three stops: desktop, tablet, phone. */\n  stops?: DeviceStop[];\n  /** Phone to desktop instead of desktop to phone. */\n  reverse?: boolean;\n  accent?: string;\n  speed?: number;\n  theme?: \"light\" | \"dark\";\n  /** Thumbnails for the six project cards. */\n  images?: string[];\n  /** gradients.sh preset for the ground. Defaults to \"europa\" (dark) or \"paper-crease\" (light). */\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n}\n\nconst DEFAULT_STOPS: DeviceStop[] = [\n  { label: \"Desktop\", width: 1440, height: 900 },\n  { label: \"Tablet\", width: 834, height: 1112 },\n  { label: \"Phone\", width: 390, height: 844 },\n];\n\nconst LAYOUT = {\n  wide: { dw: 1280, dh: 720, maxW: 820, maxH: 500, cy: 322, segY: 618 },\n  tall: { dw: 720, dh: 1280, maxW: 640, maxH: 700, cy: 700, segY: 1140 },\n};\nconst CLICK_A = 50;\nconst CLICK_B = 124;\nconst MORPH = 44;\n\nconst inOut = Easing.bezier(0.65, 0, 0.35, 1);\nconst MORPH_SPRING = { damping: 200 };\nconst HEAVY = { mass: 1, damping: 26, stiffness: 120 };\nconst CARD = { mass: 0.8, damping: 18, stiffness: 160 };\nconst CHIP = { mass: 0.5, damping: 12, stiffness: 200 };\nconst ease = Easing.bezier(0.22, 1, 0.36, 1);\nconst clamp01 = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\nconst seg = (t: number, a: number, b: number, c: number) =>\n  t <= 1 ? lerp(a, b, t) : lerp(b, c, t - 1);\nconst range = (t: number, a: number, b: number) =>\n  interpolate(t, [a, b], [0, 1], clamp01);\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\nconst PALETTES = {\n  light: {\n    canvas: \"#F1F1F2\",\n    dot: \"rgba(0,0,0,0.09)\",\n    chrome: \"#F4F4F5\",\n    app: \"#FFFFFF\",\n    side: \"#FAFAFA\",\n    border: \"#EAEAEE\",\n    text: \"#111113\",\n    muted: \"#6E6E78\",\n    faint: \"#B9B9C2\",\n    chip: \"#F2F2F4\",\n    pill: \"#FFFFFF\",\n    good: \"#16A34A\",\n  },\n  dark: {\n    canvas: \"#09090A\",\n    dot: \"rgba(255,255,255,0.07)\",\n    chrome: \"#1A1A1D\",\n    app: \"#0F0F11\",\n    side: \"#0C0C0E\",\n    border: \"#232327\",\n    text: \"#EDEDEF\",\n    muted: \"#8E8E98\",\n    faint: \"#4A4A52\",\n    chip: \"#1C1C20\",\n    pill: \"#17171A\",\n    good: \"#22C55E\",\n  },\n};\ntype Palette = (typeof PALETTES)[\"light\"];\n\nconst PATHS: Record<string, string> = {\n  home: \"M4 11l8-7 8 7v9h-5v-6H9v6H4z\",\n  folder: \"M3 6h7l2 2h9v11H3z\",\n  grid: \"M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z\",\n  play: \"M6 4l14 8-14 8z\",\n  image: \"M4 5h16v14H4zM4 15l5-5 4 4 3-3 4 4\",\n  gear: \"M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1\",\n  search: \"M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-4-4\",\n  bell: \"M6 16V11a6 6 0 0 1 12 0v5l2 2H4zM10 20a2 2 0 0 0 4 0\",\n  plus: \"M12 5v14M5 12h14\",\n  menu: \"M4 7h16M4 12h16M4 17h16\",\n  monitor: \"M3 4h18v12H3zM8 20h8M12 16v4\",\n  tablet: \"M6 3h12v18H6zM11 18h2\",\n  phone: \"M8 3h8v18H8zM11 18h2\",\n};\n\nfunction G({\n  n,\n  color,\n  size = 18,\n  sw = 1.5,\n}: {\n  n: string;\n  color: string;\n  size?: number;\n  sw?: number;\n}) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 24 24\"\n      aria-hidden=\"true\"\n      style={{ display: \"block\", flexShrink: 0 }}\n    >\n      <path\n        d={PATHS[n]}\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={sw}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nconst NAV = [\n  { icon: \"home\", label: \"Home\" },\n  { icon: \"folder\", label: \"Projects\" },\n  { icon: \"grid\", label: \"Templates\" },\n  { icon: \"play\", label: \"Renders\" },\n  { icon: \"image\", label: \"Assets\" },\n  { icon: \"gear\", label: \"Settings\" },\n];\n\nconst KPIS = [\n  {\n    label: \"Renders this week\",\n    value: \"128\",\n    delta: \"+18%\",\n    pts: [4, 6, 5, 8, 7, 11, 14],\n  },\n  {\n    label: \"Minutes rendered\",\n    value: \"342\",\n    delta: \"+9%\",\n    pts: [6, 5, 7, 6, 9, 8, 10],\n  },\n  {\n    label: \"Avg. render time\",\n    value: \"41s\",\n    delta: \"−12%\",\n    pts: [12, 11, 12, 9, 8, 8, 6],\n  },\n];\n\nconst PROJECTS = [\n  {\n    title: \"Launch teaser\",\n    meta: \"Edited 2h ago · 1080p\",\n    dur: \"0:24\",\n  },\n  {\n    title: \"Onboarding walkthrough\",\n    meta: \"Edited yesterday · 4K\",\n    dur: \"1:12\",\n  },\n  {\n    title: \"Changelog 2.4\",\n    meta: \"Edited Mon · 1080p\",\n    dur: \"0:38\",\n  },\n  {\n    title: \"Customer story: Fernway\",\n    meta: \"Edited Sep 28 · 1080p\",\n    dur: \"2:05\",\n  },\n  {\n    title: \"Pricing explainer\",\n    meta: \"Edited Sep 24 · 720p\",\n    dur: \"0:45\",\n  },\n  {\n    title: \"Feature drop: Captions\",\n    meta: \"Edited Sep 19 · 1080p\",\n    dur: \"0:31\",\n  },\n];\n\nconst DEFAULT_IMAGES = [1, 2, 3, 4, 5, 6].map((n) =>\n  staticFile(`reel/reel-${n}.jpg`),\n);\n\nfunction Thumb({\n  p,\n  i,\n  src,\n  accent,\n}: {\n  p: (typeof PROJECTS)[number];\n  i: number;\n  src: string;\n  accent: string;\n}) {\n  const rendering = i === 1;\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        background: \"#1A1A1D\",\n        overflow: \"hidden\",\n      }}\n    >\n      <Img\n        src={src}\n        style={{\n          width: \"100%\",\n          height: \"100%\",\n          objectFit: \"cover\",\n          display: \"block\",\n          filter: rendering ? \"saturate(0.6) brightness(0.8)\" : undefined,\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          right: 10,\n          bottom: rendering ? 16 : 10,\n          padding: \"3px 8px\",\n          borderRadius: 6,\n          background: \"rgba(10,10,12,0.66)\",\n          color: \"#fff\",\n          fontSize: 12.5,\n          fontWeight: 400,\n          fontVariantNumeric: \"tabular-nums\",\n        }}\n      >\n        {rendering ? \"Rendering 64%\" : p.dur}\n      </div>\n      {rendering ? (\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            right: 0,\n            bottom: 0,\n            height: 4,\n            background: \"rgba(255,255,255,0.25)\",\n          }}\n        >\n          <div style={{ width: \"64%\", height: \"100%\", background: accent }} />\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\nfunction Dashboard({\n  t,\n  vw,\n  vh,\n  c,\n  accent,\n  images,\n}: {\n  t: number;\n  vw: number;\n  vh: number;\n  c: Palette;\n  accent: string;\n  images: string[];\n}) {\n  const tp = range(t, 1, 2);\n  const label = 1 - range(t, 0, 0.55);\n  const statusH = seg(t, 0, 24, 46);\n  const topH = seg(t, 64, 60, 54);\n  const sideW = seg(t, 232, 72, 0);\n  const pad = seg(t, 32, 24, 16);\n  const cx = sideW + pad;\n  const cw = vw - sideW - 2 * pad;\n  const top0 = statusH + topH + pad;\n  const gap = seg(t, 16, 16, 12);\n  const third = (cw - 2 * gap) / 3;\n  const kpiW = lerp(third, cw * 0.72, ease(tp));\n  const kpiH = 108;\n  const secY = top0 + 72 + kpiH + 30;\n  const gridY = secY + 38;\n  const layoutAt = (n: number) =>\n    PROJECTS.map((_, i) => {\n      const w = (cw - (n - 1) * gap) / n;\n      const h = (w * 9) / 16 + 62;\n      return {\n        x: cx + (i % n) * (w + gap),\n        y: gridY + Math.floor(i / n) * (h + gap),\n        w,\n      };\n    });\n  const [from, to, u] =\n    t <= 1 ? [layoutAt(3), layoutAt(2), t] : [layoutAt(2), layoutAt(1), t - 1];\n  const kAt = (i: number) => inOut(range(u, i * 0.05, 0.75 + i * 0.05));\n  const rightPad = seg(t, 24, 20, 16);\n  const avatarX = vw - rightPad - 32;\n  const btnW = seg(t, 116, 116, 34);\n  const btnX = avatarX - 12 - btnW;\n  const bellX = btnX - 12 - 34;\n  const searchW = seg(t, 260, 34, 0);\n  const searchX = bellX - 10 - searchW;\n  const phoneOnly = range(t, 1.35, 1.9);\n  const txt = { position: \"absolute\" as const, whiteSpace: \"nowrap\" as const };\n\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        background: c.app,\n        color: c.text,\n        overflow: \"hidden\",\n      }}\n    >\n      <div\n        style={{\n          ...txt,\n          left: 0,\n          top: 0,\n          width: vw,\n          height: statusH,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"space-between\",\n          padding: `0 ${seg(t, 20, 20, 30)}px`,\n          boxSizing: \"border-box\",\n          fontSize: seg(t, 12, 13, 16),\n          fontWeight: 400,\n          opacity: range(t, 0.5, 1),\n        }}\n      >\n        <span>9:41</span>\n        <span style={{ display: \"flex\", gap: 5, alignItems: \"center\" }}>\n          <span\n            style={{\n              width: 17,\n              height: 10,\n              borderRadius: 2,\n              background: `linear-gradient(90deg, ${c.text} 0 20%, transparent 20% 30%, ${c.text} 30% 50%, transparent 50% 60%, ${c.text} 60% 80%, transparent 80%)`,\n            }}\n          />\n          <span\n            style={{\n              width: 24,\n              height: 11,\n              borderRadius: 3,\n              border: `1.5px solid ${c.text}`,\n              boxSizing: \"border-box\",\n              padding: 1,\n            }}\n          >\n            <span\n              style={{\n                display: \"block\",\n                width: \"70%\",\n                height: \"100%\",\n                borderRadius: 1,\n                background: c.text,\n              }}\n            />\n          </span>\n        </span>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: statusH,\n          width: Math.max(sideW, 0.01),\n          bottom: 0,\n          background: c.side,\n          borderRight: sideW > 1 ? `1px solid ${c.border}` : \"none\",\n          overflow: \"hidden\",\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            left: lerp(22, 20, label) - 80 * tp,\n            top: 18,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 10,\n          }}\n        >\n          <div\n            style={{\n              width: 28,\n              height: 28,\n              borderRadius: 8,\n              background: accent,\n              display: \"flex\",\n              alignItems: \"center\",\n              justifyContent: \"center\",\n            }}\n          >\n            <svg viewBox=\"0 0 124.06 134.26\" height={14} aria-hidden>\n              <path fill=\"#fff\" d={REMOCN_R_PATH} />\n            </svg>\n          </div>\n          <span\n            style={{\n              fontSize: 17,\n              fontWeight: 400,\n              letterSpacing: -0.3,\n              opacity: label,\n            }}\n          >\n            remocn\n          </span>\n        </div>\n        {NAV.map((n, i) => {\n          const y = 76 + i * 42;\n          const active = i === 1;\n          const pillW = lerp(48, sideW - 24, label);\n          const shift = -80 * tp;\n          return (\n            <div key={n.label}>\n              {active && (\n                <div\n                  style={{\n                    position: \"absolute\",\n                    left: 12 + shift,\n                    top: y - 9,\n                    width: pillW,\n                    height: 36,\n                    borderRadius: 9,\n                    background: withAlpha(accent, 0.1),\n                  }}\n                />\n              )}\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: lerp(27, 24, label) + shift,\n                  top: y,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  gap: 12,\n                }}\n              >\n                <G n={n.icon} color={active ? accent : c.muted} />\n                <span\n                  style={{\n                    fontSize: 14.5,\n                    fontWeight: 400,\n                    color: active ? c.text : c.muted,\n                    opacity: label,\n                  }}\n                >\n                  {n.label}\n                </span>\n              </div>\n            </div>\n          );\n        })}\n        <div\n          style={{\n            position: \"absolute\",\n            left: 16,\n            right: 16,\n            bottom: 20,\n            padding: 14,\n            borderRadius: 12,\n            border: `1px solid ${c.border}`,\n            background: c.app,\n            opacity: label,\n            fontSize: 13,\n          }}\n        >\n          <div style={{ fontWeight: 400 }}>Render minutes</div>\n          <div style={{ color: c.muted, marginTop: 3 }}>312 of 500 used</div>\n          <div\n            style={{\n              marginTop: 10,\n              height: 6,\n              borderRadius: 6,\n              background: c.chip,\n            }}\n          >\n            <div\n              style={{\n                width: \"62%\",\n                height: \"100%\",\n                borderRadius: 6,\n                background: accent,\n              }}\n            />\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: sideW,\n          top: statusH,\n          right: 0,\n          height: topH,\n          borderBottom: `1px solid ${c.border}`,\n        }}\n      >\n        <div\n          style={{\n            ...txt,\n            left: pad,\n            top: topH / 2 - 10,\n            fontSize: 14.5,\n            fontWeight: 400,\n            color: c.muted,\n            opacity: 1 - range(t, 1.05, 1.5),\n          }}\n        >\n          Projects <span style={{ color: c.faint, margin: \"0 6px\" }}>/</span>\n          <span style={{ color: c.text }}>All projects</span>\n        </div>\n        <div\n          style={{\n            ...txt,\n            left: pad,\n            top: topH / 2 - 12,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 12,\n            opacity: phoneOnly,\n            transform: `translateX(${(1 - phoneOnly) * -12}px)`,\n          }}\n        >\n          <G n=\"menu\" color={c.text} size={24} />\n          <span style={{ fontSize: 19, fontWeight: 400, letterSpacing: -0.3 }}>\n            remocn\n          </span>\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: searchX,\n          top: statusH + topH / 2 - 18,\n          width: searchW + 2,\n          height: 36,\n          borderRadius: 9,\n          border: `1px solid ${c.border}`,\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 8,\n          paddingLeft: 8,\n          boxSizing: \"border-box\",\n          overflow: \"hidden\",\n          opacity: 1 - range(t, 1, 1.4),\n          fontSize: 13.5,\n          color: c.muted,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        <G n=\"search\" color={c.muted} size={17} />\n        <span style={{ opacity: label, flex: 1 }}>Search projects…</span>\n        <span\n          style={{\n            opacity: label,\n            fontSize: 11.5,\n            padding: \"2px 6px\",\n            marginRight: 8,\n            borderRadius: 5,\n            background: c.chip,\n          }}\n        >\n          ⌘K\n        </span>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: bellX + 8,\n          top: statusH + topH / 2 - 9,\n          opacity: 1 - range(t, 1, 1.4),\n        }}\n      >\n        <G n=\"bell\" color={c.muted} />\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: btnX,\n          top: statusH + topH / 2 - 17,\n          width: btnW,\n          height: 34,\n          borderRadius: 9,\n          background: accent,\n          color: \"#fff\",\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          gap: 6,\n          fontSize: 13.5,\n          fontWeight: 400,\n          overflow: \"hidden\",\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        <G n=\"plus\" color=\"#fff\" size={16} sw={2.2} />\n        <span\n          style={{\n            opacity: 1 - range(t, 1.2, 1.6),\n            display: btnW < 60 ? \"none\" : \"inline\",\n          }}\n        >\n          New video\n        </span>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: avatarX,\n          top: statusH + topH / 2 - 16,\n          width: 32,\n          height: 32,\n          borderRadius: 99,\n          background: \"#B86A52\",\n          color: \"#fff\",\n          fontSize: 12,\n          fontWeight: 400,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n        }}\n      >\n        MC\n      </div>\n      <div style={{ ...txt, left: cx, top: top0 }}>\n        <div\n          style={{\n            fontSize: seg(t, 26, 25, 23),\n            fontWeight: 400,\n            letterSpacing: -0.6,\n          }}\n        >\n          Good morning, Maya\n        </div>\n        <div style={{ fontSize: 14.5, color: c.muted, marginTop: 6 }}>\n          3 renders finished overnight.\n        </div>\n      </div>\n      {KPIS.map((m, i) => {\n        const x = cx + i * (kpiW + gap);\n        const max = Math.max(...m.pts);\n        const d = m.pts\n          .map(\n            (v, j) => `${j === 0 ? \"M\" : \"L\"}${j * 12} ${30 - (v / max) * 26}`,\n          )\n          .join(\" \");\n        return (\n          <div\n            key={m.label}\n            style={{\n              position: \"absolute\",\n              left: x,\n              top: top0 + 72,\n              width: kpiW,\n              height: kpiH,\n              borderRadius: 12,\n              border: `1px solid ${c.border}`,\n              background: c.app,\n              boxSizing: \"border-box\",\n              padding: 16,\n              overflow: \"hidden\",\n            }}\n          >\n            <div style={{ fontSize: 13, color: c.muted, whiteSpace: \"nowrap\" }}>\n              {m.label}\n            </div>\n            <div\n              style={{\n                display: \"flex\",\n                alignItems: \"baseline\",\n                gap: 8,\n                marginTop: 10,\n              }}\n            >\n              <span\n                style={{ fontSize: 28, fontWeight: 400, letterSpacing: -0.6 }}\n              >\n                {m.value}\n              </span>\n              <span style={{ fontSize: 12, fontWeight: 400, color: c.good }}>\n                {m.delta}\n              </span>\n            </div>\n            <svg\n              width={74}\n              height={32}\n              style={{\n                position: \"absolute\",\n                right: 16,\n                bottom: 18,\n                overflow: \"visible\",\n              }}\n              aria-hidden=\"true\"\n            >\n              <path\n                d={d}\n                fill=\"none\"\n                stroke={accent}\n                strokeWidth={2}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </div>\n        );\n      })}\n      <div\n        style={{\n          ...txt,\n          left: cx,\n          top: secY,\n          width: cw,\n          display: \"flex\",\n          justifyContent: \"space-between\",\n          alignItems: \"center\",\n        }}\n      >\n        <span style={{ fontSize: 17, fontWeight: 400 }}>Recent projects</span>\n        <span style={{ fontSize: 13.5, color: c.muted, fontWeight: 400 }}>\n          View all\n        </span>\n      </div>\n      {PROJECTS.map((p, i) => {\n        const k = kAt(i);\n        const nFrom = t <= 1 ? 3 : 2;\n        const hops =\n          Math.floor(i / nFrom) !== Math.floor(i / (nFrom - 1)) &&\n          to[i].x < from[i].x - 1;\n        const at = hops ? (k < 0.5 ? 0 : 1) : k;\n        const x = lerp(from[i].x, to[i].x, at);\n        const y = lerp(from[i].y, to[i].y, hops ? at : k);\n        const w = lerp(from[i].w, to[i].w, k);\n        const hop = !hops\n          ? 1\n          : k < 0.5\n            ? 1 - k * 2\n            : interpolate(u, [0.5, 0.8], [0, 1], clamp01);\n        return (\n          <div\n            key={p.title}\n            style={{\n              position: \"absolute\",\n              left: x,\n              top: y,\n              width: w,\n              height: (w * 9) / 16 + 62,\n              borderRadius: 12,\n              border: `1px solid ${c.border}`,\n              background: c.app,\n              overflow: \"hidden\",\n              boxSizing: \"border-box\",\n              opacity: hop,\n              transform: `scale(${0.96 + 0.04 * hop})`,\n            }}\n          >\n            <div style={{ position: \"relative\", height: (w * 9) / 16 }}>\n              <Thumb\n                p={p}\n                i={i}\n                src={images[i % images.length]}\n                accent={accent}\n              />\n            </div>\n            <div style={{ padding: \"11px 14px\", whiteSpace: \"nowrap\" }}>\n              <div style={{ fontSize: 14.5, fontWeight: 400 }}>{p.title}</div>\n              <div style={{ fontSize: 12.5, color: c.muted, marginTop: 4 }}>\n                {p.meta}\n              </div>\n            </div>\n          </div>\n        );\n      })}\n      {tp > 0 && (\n        <div\n          style={{\n            position: \"absolute\",\n            left: 0,\n            top: vh - 84 + (1 - ease(tp)) * 96,\n            width: vw,\n            height: 84,\n            background: withAlpha(c.app, 0.94),\n            borderTop: `1px solid ${c.border}`,\n            display: \"flex\",\n            justifyContent: \"space-around\",\n            paddingTop: 10,\n            boxSizing: \"border-box\",\n          }}\n        >\n          {NAV.filter((_, i) => i !== 2).map((n, i) => (\n            <div\n              key={n.label}\n              style={{\n                display: \"flex\",\n                flexDirection: \"column\",\n                alignItems: \"center\",\n                gap: 4,\n                fontSize: 11,\n                fontWeight: 400,\n                color: i === 1 ? accent : c.muted,\n              }}\n            >\n              <G n={n.icon} color={i === 1 ? accent : c.muted} size={22} />\n              {n.label}\n            </div>\n          ))}\n          <div\n            style={{\n              position: \"absolute\",\n              left: vw / 2 - 66,\n              bottom: 8,\n              width: 132,\n              height: 5,\n              borderRadius: 5,\n              background: c.text,\n              opacity: 0.85,\n            }}\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction Cursor({\n  x,\n  y,\n  press,\n  opacity,\n}: {\n  x: number;\n  y: number;\n  press: number;\n  opacity: number;\n}) {\n  return (\n    <svg\n      width={22}\n      height={26}\n      viewBox=\"0 0 20 24\"\n      style={{\n        position: \"absolute\",\n        left: x - 2.2,\n        top: y - 2.2,\n        opacity,\n        transformOrigin: \"2px 2px\",\n        transform: `scale(${1 - 0.04 * press})`,\n        filter: \"drop-shadow(0 2px 4px rgba(0,0,0,0.3))\",\n        overflow: \"visible\",\n      }}\n    >\n      <path\n        d=\"M2 2 L2 19.5 L6.6 15.4 L9.8 22.4 L12.9 21 L9.8 14.2 L16 14.2 Z\"\n        fill=\"#FFFFFF\"\n        stroke=\"#111114\"\n        strokeWidth={1.2}\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nexport function DeviceMorph({\n  children,\n  src,\n  stops = DEFAULT_STOPS,\n  reverse = false,\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"light\",\n  images = DEFAULT_IMAGES,\n  ground,\n  groundParams,\n}: DeviceMorphProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width: W, height: H, fps } = useVideoConfig();\n  const tall = H > W * 1.2;\n  const L = tall ? LAYOUT.tall : LAYOUT.wide;\n  const k = W / L.dw;\n  const c = PALETTES[theme];\n  const dark = theme === \"dark\";\n  const [s0, s1, s2] = [\n    stops[0] ?? DEFAULT_STOPS[0],\n    stops[1] ?? DEFAULT_STOPS[1],\n    stops[2] ?? DEFAULT_STOPS[2],\n  ];\n\n  const morphAt = (at: number) =>\n    spring({\n      frame: frame - at - 2,\n      fps,\n      config: MORPH_SPRING,\n      durationInFrames: MORPH,\n    });\n  const stepA = morphAt(CLICK_A);\n  const stepB = morphAt(CLICK_B);\n  const t = reverse ? 2 - stepA - stepB : stepA + stepB;\n\n  const fit = (s: DeviceStop, chrome: number) =>\n    Math.min(L.maxW / s.width, (L.maxH - chrome) / s.height);\n  const vw = seg(t, s0.width, s1.width, s2.width);\n  const vh = seg(t, s0.height, s1.height, s2.height);\n  const sc = seg(t, fit(s0, 34), fit(s1, 24), fit(s2, 20));\n  const screenW = vw * sc;\n  const screenH = vh * sc;\n  const barH = seg(t, 34, 0, 0);\n  const bezel = seg(t, 0, 12, 10);\n  const radius = seg(t, 12, 30, 42);\n  const outerW = screenW + 2 * bezel;\n  const outerH = screenH + barH + 2 * bezel;\n  const fx = L.dw / 2 - outerW / 2;\n  const fy = L.cy - outerH / 2;\n  const enter = spring({ frame, fps, config: HEAVY });\n  const chromeIn = spring({ frame: frame - 6, fps, config: CARD });\n\n  const chipW = 168;\n  const segW = chipW * 3 + 8;\n  const segX = L.dw / 2 - segW / 2;\n  const segY = L.segY;\n  const chipCenter = (i: number) => ({\n    x: segX + 4 + i * chipW + chipW / 2,\n    y: segY + 24,\n  });\n  const [firstTarget, secondTarget] = reverse ? [1, 0] : [1, 2];\n  const a = chipCenter(firstTarget);\n  const b = chipCenter(secondTarget);\n  const keys = [\n    { f: 20, x: L.dw * 0.74, y: segY - 80 },\n    { f: CLICK_A - 3, x: a.x + 8, y: a.y + 4 },\n    { f: CLICK_A + 14, x: a.x + 8, y: a.y + 4 },\n    { f: CLICK_A + 34, x: a.x + 30, y: a.y - 40 },\n    { f: CLICK_B - 24, x: a.x + 30, y: a.y - 40 },\n    { f: CLICK_B - 3, x: b.x + 8, y: b.y + 4 },\n    { f: CLICK_B + 14, x: b.x + 8, y: b.y + 4 },\n    { f: CLICK_B + 40, x: b.x + 120, y: b.y - 30 },\n  ];\n  let cur = keys[keys.length - 1];\n  for (let i = 1; i < keys.length; i++) {\n    if (frame <= keys[i].f) {\n      const p = ease(range(frame, keys[i - 1].f, keys[i].f));\n      cur = {\n        f: 0,\n        x: lerp(keys[i - 1].x, keys[i].x, p),\n        y: lerp(keys[i - 1].y, keys[i].y, p) - Math.sin(p * Math.PI) * 22,\n      };\n      break;\n    }\n  }\n  const pressAt = (at: number) =>\n    spring({ frame: frame - (at - 2), fps, config: CHIP }) -\n    spring({ frame: frame - (at + 2), fps, config: CHIP });\n  const pressA = pressAt(CLICK_A);\n  const pressB = pressAt(CLICK_B);\n  const press = pressA + pressB;\n  const curOpacity =\n    range(frame, 18, 24) * (1 - range(frame, CLICK_B + 30, CLICK_B + 40));\n  const deviceBg = interpolateColors(\n    t,\n    [0, 0.35, 0.7],\n    [c.chrome, \"#0B0B0D\", \"#0B0B0D\"],\n  );\n  const nearest = Math.round(t);\n  const handle = (x: number, y: number) => (\n    <div\n      key={`${x}-${y}`}\n      style={{\n        position: \"absolute\",\n        left: x - 3.5,\n        top: y - 3.5,\n        width: 7,\n        height: 7,\n        background: \"#fff\",\n        border: `1px solid ${accent}`,\n        boxSizing: \"border-box\",\n        borderRadius: 1.5,\n      }}\n    />\n  );\n  const o = 8;\n  const dims = `${Math.round(vw)} × ${Math.round(vh)}`;\n\n  return (\n    <AbsoluteFill\n      style={{\n        background: dark ? \"#01040a\" : \"#eeece8\",\n        overflow: \"hidden\",\n      }}\n    >\n      <AbsoluteFill style={{ transform: `scale(${1.04 + 0.03 * (t / 2)})` }}>\n        <GshGround\n          preset={ground ?? (dark ? \"europa\" : \"paper-crease\")}\n          params={groundParams}\n          timeOffset={4}\n          timeScale={0.5}\n        />\n      </AbsoluteFill>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          width: L.dw,\n          height: L.dh,\n          transform: `scale(${k})`,\n          transformOrigin: \"0 0\",\n          fontFamily,\n          color: c.text,\n          WebkitFontSmoothing: \"antialiased\",\n        }}\n      >\n        {tall ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: 64,\n              right: 64,\n              top: 150,\n              opacity: chromeIn,\n              transform: `translateY(${(1 - chromeIn) * 24}px)`,\n            }}\n          >\n            <div\n              style={{\n                position: \"relative\",\n                height: 96,\n                fontSize: 88,\n                fontWeight: 300,\n                lineHeight: 1,\n                letterSpacing: \"-0.04em\",\n                color: c.text,\n              }}\n            >\n              {[s0, s1, s2].map((s, i) => {\n                const vis = Math.max(0, 1 - Math.abs(t - i) * 2);\n                if (vis <= 0.001) return null;\n                return (\n                  <div\n                    key={s.label}\n                    style={{\n                      position: \"absolute\",\n                      left: 0,\n                      top: 0,\n                      opacity: vis,\n                      transform: `translateY(${(t - i) * -40}px)`,\n                      filter: `blur(${(1 - vis) * 8}px)`,\n                    }}\n                  >\n                    {s.label}\n                  </div>\n                );\n              })}\n            </div>\n            <div\n              style={{\n                marginTop: 18,\n                fontFamily: mono,\n                fontSize: 28,\n                color: c.muted,\n                fontVariantNumeric: \"tabular-nums\",\n              }}\n            >\n              {dims}\n            </div>\n          </div>\n        ) : (\n          <div\n            style={{\n              position: \"absolute\",\n              left: fx - o,\n              top: fy - o - 28,\n              fontFamily: mono,\n              fontSize: 16,\n              color: c.muted,\n              whiteSpace: \"nowrap\",\n              fontVariantNumeric: \"tabular-nums\",\n              opacity: chromeIn,\n            }}\n          >\n            <span style={{ color: c.text }}>{[s0, s1, s2][nearest].label}</span>\n            {\"  \"}\n            {dims}\n          </div>\n        )}\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: fx - o,\n            top: fy - o,\n            width: outerW + 2 * o,\n            height: outerH + 2 * o,\n            border: `1px solid ${withAlpha(accent, 0.6)}`,\n            borderRadius: radius + o,\n            boxSizing: \"border-box\",\n            opacity: chromeIn,\n          }}\n        >\n          {[\n            [0, 0],\n            [1, 0],\n            [0, 1],\n            [1, 1],\n          ].map(([u, v]) => handle(u * (outerW + 2 * o), v * (outerH + 2 * o)))}\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: fx,\n            top: fy + (1 - enter) * 36,\n            width: outerW,\n            height: outerH,\n            borderRadius: radius,\n            background: deviceBg,\n            padding: bezel,\n            boxSizing: \"border-box\",\n            boxShadow: dark\n              ? \"0 0 0 1px rgba(255,255,255,0.1), 0 40px 80px -24px rgba(0,0,0,0.85)\"\n              : \"0 0 0 1px rgba(16,24,40,0.08), 0 2px 6px rgba(16,24,40,0.06), 0 40px 80px -28px rgba(16,24,40,0.4)\",\n            opacity: interpolate(enter, [0, 0.4], [0, 1], clamp01),\n            transform: `scale(${0.96 + 0.04 * enter})`,\n          }}\n        >\n          {barH > 0.5 && (\n            <div\n              style={{\n                position: \"relative\",\n                height: barH,\n                overflow: \"hidden\",\n                opacity: 1 - range(t, 0, 0.2),\n              }}\n            >\n              {[0, 1, 2].map((i) => (\n                <div\n                  key={i}\n                  style={{\n                    position: \"absolute\",\n                    left: 14 + i * 18,\n                    top: barH / 2 - 6,\n                    width: 12,\n                    height: 12,\n                    borderRadius: 12,\n                    background: dark ? \"#3A3A40\" : \"#D4D4D8\",\n                  }}\n                />\n              ))}\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: \"50%\",\n                  top: barH / 2 - 11,\n                  width: 300,\n                  marginLeft: -150,\n                  height: 22,\n                  borderRadius: 7,\n                  background: dark ? \"#0F0F11\" : \"#FFFFFF\",\n                  boxShadow: `inset 0 0 0 1px ${c.border}`,\n                  fontSize: 12,\n                  color: c.muted,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  justifyContent: \"center\",\n                }}\n              >\n                remocn.dev/projects\n              </div>\n            </div>\n          )}\n          <div\n            style={{\n              position: \"relative\",\n              width: screenW,\n              height: screenH,\n              borderRadius: Math.max(0, radius - bezel) * range(t, 0, 0.5),\n              overflow: \"hidden\",\n              background: c.app,\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                left: 0,\n                top: 0,\n                width: vw,\n                height: vh,\n                transform: `scale(${sc})`,\n                transformOrigin: \"0 0\",\n              }}\n            >\n              {children ? (\n                <div style={{ position: \"absolute\", inset: 0 }}>{children}</div>\n              ) : src ? (\n                <Img\n                  src={src}\n                  style={{\n                    width: \"100%\",\n                    height: \"100%\",\n                    objectFit: \"cover\",\n                    objectPosition: \"top\",\n                  }}\n                />\n              ) : (\n                <Dashboard\n                  t={t}\n                  vw={vw}\n                  vh={vh}\n                  c={c}\n                  accent={accent}\n                  images={images}\n                />\n              )}\n            </div>\n            <div\n              style={{\n                position: \"absolute\",\n                left: screenW / 2 - 40,\n                top: 8,\n                width: 80,\n                height: 22,\n                borderRadius: 20,\n                background: \"#000\",\n                opacity: range(t, 1.45, 1.9),\n                transform: `scale(${0.6 + 0.4 * range(t, 1.45, 1.9)})`,\n              }}\n            />\n          </div>\n          <div\n            style={{\n              position: \"absolute\",\n              left: outerW / 2 - 3,\n              top: bezel / 2 - 3,\n              width: 6,\n              height: 6,\n              borderRadius: 6,\n              background: \"#2A2A2E\",\n              opacity: range(t, 0.5, 1) * (1 - range(t, 1.2, 1.5)),\n            }}\n          />\n        </div>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: segX,\n            top: segY,\n            width: segW,\n            height: 48,\n            borderRadius: 13,\n            background: c.pill,\n            boxSizing: \"border-box\",\n            boxShadow: dark\n              ? \"inset 0 0 0 1px rgba(255,255,255,0.08), inset 0 1px 0 rgba(255,255,255,0.05), 0 12px 32px -12px rgba(0,0,0,0.7)\"\n              : \"0 0 0 1px rgba(16,24,40,0.06), 0 1px 2px rgba(16,24,40,0.06), 0 12px 32px -12px rgba(16,24,40,0.25)\",\n            opacity: chromeIn,\n            transform: `translateY(${(1 - chromeIn) * 16}px)`,\n          }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              left: 4 + t * chipW,\n              top: 4,\n              width: chipW,\n              height: 40,\n              borderRadius: 9,\n              background: withAlpha(accent, dark ? 0.22 : 0.1),\n            }}\n          />\n          {[s0, s1, s2].map((s, i) => {\n            const on = 1 - Math.min(1, Math.abs(t - i));\n            const pressed =\n              (i === firstTarget ? pressA : 0) +\n              (i === secondTarget ? pressB : 0);\n            return (\n              <div\n                key={s.label}\n                style={{\n                  position: \"absolute\",\n                  left: 4 + i * chipW,\n                  top: 4,\n                  width: chipW,\n                  height: 40,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  justifyContent: \"center\",\n                  gap: 8,\n                  fontSize: 16,\n                  fontWeight: 400,\n                  color: interpolateColors(on, [0, 1], [c.muted, c.text]),\n                  transform: `scale(${1 - 0.04 * pressed})`,\n                }}\n              >\n                <G\n                  n={[\"monitor\", \"tablet\", \"phone\"][i]}\n                  color={on > 0.5 ? accent : c.muted}\n                  size={17}\n                  sw={1.5}\n                />\n                {s.label}\n                <span\n                  style={{\n                    fontFamily: mono,\n                    fontSize: 16,\n                    color: c.muted,\n                    fontVariantNumeric: \"tabular-nums\",\n                  }}\n                >\n                  {s.width}\n                </span>\n              </div>\n            );\n          })}\n        </div>\n        <Cursor x={cur.x} y={cur.y} press={press} opacity={curOpacity} />\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/device-morph.tsx"
    }
  ],
  "type": "registry:component"
}
