{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "theme-flip",
  "title": "Theme Flip",
  "description": "A product UI flips between dark and light themes with a cursor trigger.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/theme-flip/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  spring,\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 ThemeFlipProps {\n  /** Custom \"before\" / \"after\" UI. Fallback: the built-in mock in light and dark. */\n  from?: React.ReactNode;\n  to?: React.ReactNode;\n  fromSrc?: string;\n  toSrc?: string;\n  /** Theme the built-in mock starts in. */\n  startTheme?: \"light\" | \"dark\";\n  /** Click point and flood center, in screenshot pixels. Defaults to the mock's toggle. */\n  origin?: { x: number; y: number };\n  /** Draw a floating toggle at `origin` over custom content. */\n  showToggle?: boolean;\n  showCursor?: boolean;\n  /** Flip back to the first theme after a hold. */\n  reverse?: boolean;\n  screenshotWidth?: number;\n  screenshotHeight?: number;\n  accent?: string;\n  speed?: number;\n  /** Camera zoom on the toggle around the click (1 = no push-in). */\n  zoom?: number;\n  /** gradients.sh preset for the ground. Defaults to \"charcoal-satin\". */\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n}\n\nconst SEG = 80;\nconst TOGGLE_RIGHT = 1431 - 12 - 64 - 6;\nconst DEFAULT_ORIGIN = { x: TOGGLE_RIGHT - 2 - SEG / 2, y: 33 };\n\nconst CLICK = 36;\nconst FLOOD = 26;\nconst REV = 92;\nconst ZOOM = 2.1;\nconst MOVE = Easing.bezier(0.65, 0, 0.35, 1);\nconst CAMERA = Easing.bezier(0.87, 0, 0.13, 1);\nconst ENTER = Easing.bezier(0.22, 1, 0.36, 1);\nconst WIPE = Easing.bezier(0.83, 0, 0.17, 1);\nconst CARD = { mass: 0.8, damping: 18, stiffness: 160 };\nconst CHIP = { mass: 0.5, damping: 12, stiffness: 200 };\nconst HEAVY = { mass: 1, damping: 26, stiffness: 120 };\nconst clamp = (v: number, lo: number, hi: number) =>\n  Math.min(hi, Math.max(lo, v));\nconst ramp = (frame: number, from: number, len: number, curve = MOVE) =>\n  curve(clamp((frame - from) / len, 0, 1));\n\nfunction ThemeToggle({\n  theme,\n  k,\n  hover,\n  press,\n}: {\n  theme: MockTheme;\n  k: number;\n  hover: number;\n  press: number;\n}) {\n  const c = MOCK_COLORS[theme];\n  const dark = theme === \"dark\";\n  const segs = [\n    { icon: \"sun\", label: \"Light\", on: 1 - clamp(k, 0, 1) },\n    { icon: \"moon\", label: \"Dark\", on: clamp(k, 0, 1) },\n  ];\n  const target = k < 0.5 ? 1 : 0;\n  return (\n    <div\n      style={{\n        position: \"relative\",\n        width: SEG * 2 + 4,\n        height: 32,\n        boxSizing: \"border-box\",\n        borderRadius: 9,\n        padding: 2,\n        marginRight: 6,\n        background: dark ? \"#0B0B0D\" : \"#EDEDF0\",\n        boxShadow: dark\n          ? \"inset 0 0 0 1px rgba(255,255,255,0.06)\"\n          : \"inset 0 0 0 1px rgba(16,24,40,0.05)\",\n        display: \"flex\",\n        flexShrink: 0,\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          left: 2 + k * SEG,\n          top: 2,\n          width: SEG,\n          height: 28,\n          borderRadius: 7,\n          background: dark ? \"#26272B\" : \"#FFFFFF\",\n          boxShadow: dark\n            ? \"inset 0 1px 0 rgba(255,255,255,0.06), 0 0 0 1px rgba(255,255,255,0.06)\"\n            : \"0 0 0 1px rgba(16,24,40,0.06), 0 1px 2px rgba(16,24,40,0.08)\",\n        }}\n      />\n      {segs.map((sg, i) => {\n        const lit = sg.on > 0.5;\n        const hot = i === target ? hover : 0;\n        return (\n          <div\n            key={sg.label}\n            style={{\n              position: \"relative\",\n              width: SEG,\n              display: \"flex\",\n              alignItems: \"center\",\n              justifyContent: \"center\",\n              gap: 6,\n              fontSize: 13,\n              fontWeight: 400,\n              color: lit || hot > 0.5 ? c.text : c.sub,\n              borderRadius: 7,\n              transform: `scale(${i === target ? 1 - 0.04 * press : 1})`,\n            }}\n          >\n            <Glyph name={sg.icon} size={14} color={lit ? c.text : c.sub} />\n            {sg.label}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction Cursor({ size, press }: { size: number; press: number }) {\n  return (\n    <svg\n      width={size}\n      height={size * 1.2}\n      viewBox=\"0 0 20 24\"\n      style={{\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 ThemeFlip({\n  from,\n  to,\n  fromSrc,\n  toSrc,\n  startTheme = \"light\",\n  origin,\n  showToggle = true,\n  showCursor = true,\n  reverse = false,\n  screenshotWidth = MOCK_W,\n  screenshotHeight = MOCK_H,\n  accent = \"#3B5BFD\",\n  speed = 1,\n  zoom = ZOOM,\n  ground,\n  groundParams,\n}: ThemeFlipProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width: W, height: H, fps } = useVideoConfig();\n  const tall = H > W * 1.2;\n  const u = Math.min(W / 1280, H / 720);\n  const sw = screenshotWidth;\n  const sh = screenshotHeight;\n  const themes: [MockTheme, MockTheme] =\n    startTheme === \"dark\" ? [\"dark\", \"light\"] : [\"light\", \"dark\"];\n  const custom = Boolean(from || to || fromSrc || toSrc);\n  const o = origin ?? DEFAULT_ORIGIN;\n  const back = startTheme === \"dark\" ? 1 : -1;\n\n  const s = tall ? (W * 0.9) / sw : (W * 0.76) / sw;\n  const left = (W - sw * s) / 2;\n  const enter = spring({ frame: frame + 2, fps, config: HEAVY });\n  const top = (tall ? H * 0.47 : H * 0.13) + (1 - enter) * 0.06 * H;\n  const toStage = (x: number, y: number) => ({\n    x: left + x * s,\n    y: top + y * s,\n  });\n  const flipPt = toStage(o.x, o.y);\n  const backPt = toStage(o.x + back * SEG, o.y);\n  const focus = toStage(o.x + (back * SEG) / 2, o.y);\n\n  const REV_CLICK = REV + 24;\n  const fwd = spring({ frame: frame - CLICK - 1, fps, config: CARD });\n  const bwd = reverse\n    ? spring({ frame: frame - REV_CLICK - 1, fps, config: CARD })\n    : 0;\n  const knob = fwd - bwd;\n  const k = startTheme === \"dark\" ? 1 - knob : knob;\n  const hover =\n    ramp(frame, CLICK - 6, 4, ENTER) * (1 - ramp(frame, CLICK + 2, 3)) +\n    (reverse\n      ? ramp(frame, REV_CLICK - 6, 4, ENTER) *\n        (1 - ramp(frame, REV_CLICK + 2, 3))\n      : 0);\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 press = pressAt(CLICK) + (reverse ? pressAt(REV_CLICK) : 0);\n\n  const winR = { x0: left, y0: top, x1: left + sw * s, y1: top + sh * s };\n  const maxR =\n    Math.max(\n      ...[\n        [winR.x0, winR.y0],\n        [winR.x1, winR.y0],\n        [winR.x0, winR.y1],\n        [winR.x1, winR.y1],\n      ].map(([x, y]) => Math.hypot(x - flipPt.x, y - flipPt.y)),\n    ) * 1.02;\n  const p1 = WIPE(clamp((frame - CLICK - 2) / FLOOD, 0, 1));\n  const p2 = reverse ? WIPE(clamp((frame - REV_CLICK - 2) / FLOOD, 0, 1)) : 0;\n\n  const z =\n    ramp(frame, 2, 28, CAMERA) -\n    ramp(frame, CLICK + 6, 32, CAMERA) +\n    (reverse\n      ? ramp(frame, REV, 22, CAMERA) - ramp(frame, REV_CLICK + 6, 32, CAMERA)\n      : 0);\n  const Z = 1 + (zoom - 1) * z;\n  const aim = tall ? { x: W * 0.62, y: H * 0.5 } : { x: W * 0.6, y: H * 0.32 };\n  const shot = {\n    x: focus.x + (aim.x - focus.x) * z,\n    y: focus.y + (aim.y - focus.y) * z,\n  };\n  const camera = `translate(${shot.x - focus.x * Z}px, ${shot.y - focus.y * Z}px) scale(${Z})`;\n\n  const startPt = { x: W * 0.74, y: tall ? H * 0.82 : H * 0.92 };\n  const park = { x: flipPt.x + 70 * u, y: flipPt.y + 90 * u };\n  const m1 = ramp(frame, 6, CLICK - 10, ENTER);\n  const arc = Math.sin(Math.PI * m1) * 46 * u;\n  let cur = {\n    x: startPt.x + (flipPt.x - startPt.x) * m1 + arc,\n    y: startPt.y + (flipPt.y - startPt.y) * m1 + arc * 0.3,\n  };\n  const away = ramp(frame, CLICK + 8, 22);\n  cur = {\n    x: cur.x + (park.x - flipPt.x) * away,\n    y: cur.y + (park.y - flipPt.y) * away,\n  };\n  if (reverse) {\n    const m2 = ramp(frame, REV + 2, 18, ENTER);\n    const arc2 = Math.sin(Math.PI * m2) * 20 * u;\n    cur = {\n      x: park.x + (backPt.x - park.x) * m2,\n      y: park.y + (backPt.y - park.y) * m2 + arc2,\n    };\n    const away2 = ramp(frame, REV_CLICK + 8, 22);\n    cur = { x: cur.x - away2 * 30 * u, y: cur.y + away2 * 70 * u };\n  }\n\n  const shown = clamp(p1 - p2, 0, 1);\n  const word = (idx: 0 | 1) =>\n    themes[idx] === \"dark\" ? \"Dark mode\" : \"Light mode\";\n\n  const windowLayer = (theme: MockTheme, idx: 0 | 1) => {\n    const src = idx === 0 ? fromSrc : toSrc;\n    const node = idx === 0 ? from : to;\n    const toggle = (\n      <ThemeToggle theme={theme} k={k} hover={hover} press={press} />\n    );\n    return (\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          width: sw,\n          height: sh,\n        }}\n      >\n        {src ? (\n          <Img\n            src={src}\n            style={{\n              width: sw,\n              height: sh,\n              objectFit: \"cover\",\n              display: \"block\",\n            }}\n          />\n        ) : custom ? (\n          node\n        ) : (\n          <IssuesMock\n            theme={theme}\n            accent={accent}\n            width={sw}\n            height={sh}\n            headerExtra={toggle}\n          />\n        )}\n        {custom && showToggle && (\n          <div\n            style={{\n              position: \"absolute\",\n              left: o.x - 2 - SEG * 1.5,\n              top: o.y - 16,\n            }}\n          >\n            {toggle}\n          </div>\n        )}\n      </div>\n    );\n  };\n\n  const local = (p: { x: number; y: number }) => ({\n    x: (p.x - left) / s,\n    y: (p.y - top) / s,\n  });\n  const fp = local(flipPt);\n  const bp = local(backPt);\n  const darkNow = themes[shown > 0.5 ? 1 : 0] === \"dark\";\n\n  return (\n    <AbsoluteFill\n      style={{\n        fontFamily,\n        overflow: \"hidden\",\n        background: \"#050506\",\n        WebkitFontSmoothing: \"antialiased\",\n      }}\n    >\n      <AbsoluteFill style={{ transform: `scale(${1.04 + 0.04 * z})` }}>\n        <GshGround\n          preset={ground ?? \"charcoal-satin\"}\n          params={groundParams}\n          timeOffset={6}\n          timeScale={0.45}\n        />\n      </AbsoluteFill>\n      <AbsoluteFill style={{ background: \"rgba(5,5,6,0.38)\" }} />\n      <AbsoluteFill style={{ transformOrigin: \"0 0\", transform: camera }}>\n        <div\n          style={{\n            position: \"absolute\",\n            left,\n            top,\n            width: sw,\n            height: sh,\n            transformOrigin: \"0 0\",\n            transform: `scale(${s})`,\n            borderRadius: 14,\n            overflow: \"hidden\",\n            opacity: interpolate(enter, [0, 0.3], [0, 1], {\n              extrapolateRight: \"clamp\",\n            }),\n            boxShadow: darkNow\n              ? \"0 0 0 1px rgba(255,255,255,0.12), 0 40px 90px -20px rgba(0,0,0,0.85)\"\n              : \"0 0 0 1px rgba(255,255,255,0.3), 0 40px 90px -20px rgba(0,0,0,0.7)\",\n          }}\n        >\n          {windowLayer(themes[0], 0)}\n          {p1 > 0 && (\n            <div\n              style={{\n                position: \"absolute\",\n                inset: 0,\n                clipPath: `circle(${(p1 * maxR) / s}px at ${fp.x}px ${fp.y}px)`,\n              }}\n            >\n              {windowLayer(themes[1], 1)}\n            </div>\n          )}\n          {p2 > 0 && (\n            <div\n              style={{\n                position: \"absolute\",\n                inset: 0,\n                clipPath: `circle(${(p2 * maxR) / s}px at ${bp.x}px ${bp.y}px)`,\n              }}\n            >\n              {windowLayer(themes[0], 0)}\n            </div>\n          )}\n        </div>\n        {showCursor && (\n          <div\n            style={{\n              position: \"absolute\",\n              left: cur.x - 2 * u,\n              top: cur.y - 2 * u,\n              opacity: interpolate(frame, [6, 12], [0, 1], {\n                extrapolateLeft: \"clamp\",\n                extrapolateRight: \"clamp\",\n              }),\n            }}\n          >\n            <Cursor size={17 * u} press={press} />\n          </div>\n        )}\n      </AbsoluteFill>\n      {tall ? (\n        <div\n          style={{\n            position: \"absolute\",\n            left: W * 0.08,\n            top: H * 0.2,\n            right: W * 0.08,\n          }}\n        >\n          <div\n            style={{\n              fontFamily: mono,\n              fontSize: 0.022 * H,\n              letterSpacing: \"0.04em\",\n              color: \"rgba(244,244,245,0.55)\",\n            }}\n          >\n            Appearance\n          </div>\n          <div\n            style={{\n              position: \"relative\",\n              marginTop: 0.02 * H,\n              height: 0.08 * H,\n              fontSize: 0.07 * H,\n              fontWeight: 300,\n              lineHeight: 1,\n              letterSpacing: \"-0.04em\",\n              color: \"#F4F4F5\",\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {[0, 1].map((idx) => {\n              const vis = idx === 0 ? 1 - shown : shown;\n              return (\n                <div\n                  key={idx}\n                  style={{\n                    position: \"absolute\",\n                    left: 0,\n                    top: 0,\n                    opacity: vis,\n                    transform: `translateY(${(1 - vis) * (idx === 1 ? 1 : -1) * (shown > 0.5 === (idx === 1) ? 1 : 0.6) * 0.03 * H}px)`,\n                    filter: `blur(${(1 - vis) * 8}px)`,\n                  }}\n                >\n                  {word(idx as 0 | 1)}\n                </div>\n              );\n            })}\n          </div>\n        </div>\n      ) : null}\n    </AbsoluteFill>\n  );\n}\n\ntype MockTheme = \"light\" | \"dark\";\ntype Status = \"progress\" | \"todo\" | \"backlog\" | \"done\";\nconst MOCK_W = 1440;\nconst MOCK_H = 900;\nconst SB = 232;\n\nconst PAL_KEYS = [\n  \"app\",\n  \"panel\",\n  \"border\",\n  \"hair\",\n  \"group\",\n  \"text\",\n  \"sub\",\n  \"faint\",\n  \"active\",\n  \"chip\",\n] as const;\ntype Pal = Record<(typeof PAL_KEYS)[number], string>;\nconst pal = (hexes: string) =>\n  Object.fromEntries(hexes.split(\" \").map((h, i) => [PAL_KEYS[i], h])) as Pal;\nconst MOCK_COLORS: Record<MockTheme, Pal> = {\n  light: pal(\n    \"#F4F4F5 #FFFFFF #E5E5E8 #F1F1F3 #F7F7F8 #1B1C1F #62656D #A1A4AB #E7E7EA #E3E3E6\",\n  ),\n  dark: pal(\n    \"#0E0E10 #161618 #252528 #1E1E21 #1C1C1F #E6E6E8 #8D9098 #5F626A #222226 #2C2C30\",\n  ),\n};\nconst row = { display: \"flex\", alignItems: \"center\" } as const;\nconst center = { ...row, justifyContent: \"center\" } as const;\n\nconst GLYPHS: Record<string, string> = {\n  inbox: \"M2.5 9.5h3l1 1.5h3l1-1.5h3M4 3.5h8l1.5 6V13h-11V9.5z\",\n  user: \"M8 8.5a2.75 2.75 0 1 0 0-5.5 2.75 2.75 0 0 0 0 5.5zM3 13.5c.9-2.2 2.8-3.2 5-3.2s4.1 1 5 3.2\",\n  cube: \"M8 1.8l5.5 3v6.4L8 14.2l-5.5-3V4.8zM2.5 4.8L8 7.8l5.5-3M8 7.8v6.4\",\n  layers: \"M8 2l6 3-6 3-6-3zM2 8.2l6 3 6-3M2 11.2l6 3 6-3\",\n  more: \"M3.5 8h.01M8 8h.01M12.5 8h.01\",\n  issues: \"M8 2.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11zM5.6 8l1.7 1.7 3.1-3.3\",\n  cycle: \"M13 8a5 5 0 1 1-1.6-3.7M13.2 2.6v2.6h-2.6\",\n  download: \"M8 2.5v7.5M5 7l3 3 3-3M3 12.5h10\",\n  invite:\n    \"M6 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM1.5 13.5c.6-2 2.3-3 4.5-3s3.9 1 4.5 3M12.5 5v4M10.5 7h4\",\n  link: \"M6.8 9.2l2.4-2.4M7.2 4.6l1-1a2.6 2.6 0 0 1 3.7 3.7l-1 1M8.8 11.4l-1 1a2.6 2.6 0 0 1-3.7-3.7l1-1\",\n  search: \"M7 11.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9zM10.4 10.4L14 14\",\n  compose: \"M7.5 2.5H3.5v10h10v-4M12.2 2.3l1.5 1.5-5.6 5.6-2 .5.5-2z\",\n  filter: \"M2.5 4.5h11M4.5 8h7M6.5 11.5h3\",\n  display: \"M2.5 5h6M11.5 5h2M2.5 11h2M7.5 11h6M10 3.5v3M6 9.5v3\",\n  chevron: \"M5 6.5l3 3 3-3\",\n  plus: \"M8 3.5v9M3.5 8h9\",\n  sun: \"M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM8 1.5V3M8 13v1.5M1.5 8H3M13 8h1.5M3.4 3.4l1 1M11.6 11.6l1 1M3.4 12.6l1-1M11.6 4.4l1-1\",\n  moon: \"M13.2 9.6A5.5 5.5 0 0 1 6.4 2.8a5.5 5.5 0 1 0 6.8 6.8z\",\n};\n\nfunction Glyph({\n  name,\n  size = 16,\n  color,\n}: {\n  name: string;\n  size?: number;\n  color: string;\n}) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      style={{ flexShrink: 0 }}\n    >\n      <path\n        d={GLYPHS[name]}\n        stroke={color}\n        strokeWidth={1.5}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction StatusIcon({\n  status,\n  accent,\n  c,\n}: {\n  status: Status;\n  accent: string;\n  c: Pal;\n}) {\n  const col = {\n    progress: \"#E8B100\",\n    todo: c.faint,\n    backlog: c.faint,\n    done: accent,\n  }[status];\n  const done = status === \"done\";\n  return (\n    <svg width={14} height={14} viewBox=\"0 0 14 14\" style={{ flexShrink: 0 }}>\n      <circle\n        cx={7}\n        cy={7}\n        r={done ? 6.5 : 5.8}\n        fill={done ? col : \"none\"}\n        stroke={done ? \"none\" : col}\n        strokeWidth={1.5}\n        strokeDasharray={status === \"backlog\" ? \"1.4 1.6\" : undefined}\n      />\n      {done && (\n        <path\n          d=\"M4.3 7.2l1.8 1.8 3.6-3.8\"\n          stroke=\"#fff\"\n          strokeWidth={1.5}\n          fill=\"none\"\n        />\n      )}\n      {status === \"progress\" && (\n        <path d=\"M7 3.6A3.4 3.4 0 0 1 7 10.4Z\" fill={col} />\n      )}\n    </svg>\n  );\n}\n\nfunction PriorityIcon({ p, c }: { p: number; c: Pal }) {\n  return (\n    <svg width={14} height={14} viewBox=\"0 0 14 14\" style={{ flexShrink: 0 }}>\n      {p === 4 && (\n        <rect x={0.5} y={0.5} width={13} height={13} rx={3} fill=\"#F2683C\" />\n      )}\n      {p === 4 && (\n        <path\n          d=\"M7 3.5v4.2M7 10.2v.1\"\n          stroke=\"#fff\"\n          strokeWidth={1.8}\n          strokeLinecap=\"round\"\n        />\n      )}\n      {p === 0 &&\n        [2.5, 7, 11.5].map((x) => (\n          <circle key={x} cx={x} cy={7} r={1.1} fill={c.faint} />\n        ))}\n      {p > 0 &&\n        p < 4 &&\n        [0, 1, 2].map((i) => (\n          <rect\n            key={i}\n            x={1.5 + i * 4}\n            y={9 - i * 3}\n            width={3}\n            height={4 + i * 3}\n            rx={0.8}\n            fill={i < p ? c.sub : c.chip}\n          />\n        ))}\n    </svg>\n  );\n}\n\nconst TINTS = [\n  \"#B86A52\",\n  \"#4F6FA8\",\n  \"#7A63A8\",\n  \"#3F8466\",\n  \"#A8823C\",\n  \"#A85878\",\n];\n\nfunction Avatar({ name, size = 20 }: { name?: string; size?: number }) {\n  const tint = name\n    ? TINTS[(name.charCodeAt(0) + name.length) % TINTS.length]\n    : \"\";\n  return (\n    <div\n      style={{\n        ...center,\n        width: size,\n        height: size,\n        flexShrink: 0,\n        boxSizing: \"border-box\",\n        borderRadius: size,\n        border: name ? undefined : \"1.3px dashed #9A9DA4\",\n        background: name ? tint : undefined,\n        color: \"#fff\",\n        fontSize: size * 0.42,\n        fontWeight: 400,\n      }}\n    >\n      {name?.split(\" \").map((w) => w[0])}\n    </div>\n  );\n}\n\nconst LABEL_COLORS: Record<string, string> = Object.fromEntries(\n  \"Feature:#8B5CF6 Bug:#EF4444 Motion:#0EA5E9 Docs:#3B82F6 Infra:#F97316 Captions:#EC4899 Marketing:#22C55E Research:#A1A1AA\"\n    .split(\" \")\n    .map((kv) => kv.split(\":\")),\n);\n\nconst ISSUE_DATA = `\n#progress|In Progress|4\nREM-142|4|Spring presets for page transitions|Feature,Motion|v1.4 Launch|Maya Chen|Oct 28\nREM-139|3|Fix flicker in zoom-blur at 60fps|Bug|Transitions|Leo Park|Oct 26\nREM-137|2|Docs: theme-flip props and examples|Docs|v1.4 Launch|Priya Raman|Oct 30\nREM-131|1|Render queue retries for long templates|Infra||Jonah Reyes|Nov 2\n#todo|Todo|5\nREM-148|3|Caption timing editor in Studio|Feature,Captions|Captions|Maya Chen|Nov 4\nREM-146|1|Add Lumen and Fernway to the logo wall|Marketing||Ava Kim|Nov 5\nREM-145|2|Export presets for 9:16 and 1:1|Feature|v1.4 Launch|Leo Park|Nov 6\nREM-144|2|Audit spring damping across primitives|Motion||Priya Raman|Nov 8\n#backlog|Backlog|3\nREM-151|0|Lottie import for the icon set|Research|||\nREM-150|1|Keyboard shortcuts cheat sheet|Docs||Ava Kim|\nREM-149|0|Audio-reactive backgrounds|Research,Motion|||\n#done|Done|17\nREM-128|3|Ship captions tab with 31 styles|Feature,Captions|Captions|Maya Chen|Oct 21\nREM-127|2|Build registry artifacts in CI|Infra||Jonah Reyes|Oct 20\nREM-125|1|Sponsors page with tiers|Marketing||Ava Kim|Oct 18\nREM-121|2|Zoom blur transition|Motion|Transitions|Leo Park|Oct 15\nREM-119|3|Agent-readable docs for every component|Docs||Priya Raman|Oct 12\nREM-117|2|Remotion Studio integration|Feature||Leo Park|Oct 9`;\n\nconst MOCK_GROUPS = ISSUE_DATA.trim()\n  .split(\"#\")\n  .filter(Boolean)\n  .map((block) => {\n    const [head, ...lines] = block.trim().split(\"\\n\");\n    const [status, name, count] = head.split(\"|\");\n    const issues = lines.map((l) => {\n      const [id, p, title, labels, project, who, date] = l.split(\"|\");\n      return {\n        id,\n        p: Number(p),\n        title,\n        labels: labels ? labels.split(\",\") : [],\n        project,\n        who,\n        date,\n      };\n    });\n    return { status: status as Status, name, count, issues };\n  });\n\nconst NAV =\n  \"inbox:Inbox:3|user:My issues|-Workspace|cube:Projects|layers:Views|more:More|-Your teams|=Engineering|>*issues:Issues|>cycle:Cycles|>cube:Projects|>layers:Views|-Try|download:Import issues|invite:Invite people|link:Connect GitHub\";\n\nfunction NavItem({ spec, c }: { spec: string; c: Pal }) {\n  if (spec.startsWith(\"-\"))\n    return (\n      <div\n        style={{\n          ...row,\n          height: 24,\n          marginTop: 16,\n          padding: \"0 16px\",\n          gap: 4,\n          color: c.faint,\n          fontSize: 12,\n        }}\n      >\n        {spec.slice(1)}\n        <Glyph name=\"chevron\" size={12} color={c.faint} />\n      </div>\n    );\n  const team = spec.startsWith(\"=\");\n  const nested = spec.startsWith(\">\");\n  const active = spec.includes(\"*\");\n  const [icon, label, badge] = spec.replace(/^[=>]?\\*?/, \"\").split(\":\");\n  return (\n    <div\n      style={{\n        ...row,\n        height: 28,\n        margin: \"0 8px\",\n        padding: `0 8px 0 ${nested ? 24 : 8}px`,\n        gap: 9,\n        borderRadius: 6,\n        background: active ? c.active : \"transparent\",\n        color: active || team ? c.text : c.sub,\n        fontSize: 13,\n        fontWeight: 400,\n      }}\n    >\n      {team ? (\n        <div\n          style={{\n            ...center,\n            width: 15,\n            height: 15,\n            borderRadius: 4,\n            background: \"#1FA463\",\n            color: \"#fff\",\n            fontSize: 9,\n          }}\n        >\n          E\n        </div>\n      ) : (\n        <Glyph name={icon} size={15} color={active ? c.text : c.sub} />\n      )}\n      <span style={{ flex: team ? undefined : 1 }}>{team ? icon : label}</span>\n      {team && <Glyph name=\"chevron\" size={12} color={c.faint} />}\n      {badge && <span style={{ fontSize: 11.5, color: c.faint }}>{badge}</span>}\n    </div>\n  );\n}\n\ntype MockIssue = (typeof MOCK_GROUPS)[number][\"issues\"][number];\n\nfunction IssueRow({\n  issue,\n  status,\n  accent,\n  c,\n}: {\n  issue: MockIssue;\n  status: Status;\n  accent: string;\n  c: Pal;\n}) {\n  const chip = {\n    ...row,\n    height: 22,\n    padding: \"0 9px\",\n    gap: 6,\n    borderRadius: 11,\n    border: `1px solid ${c.chip}`,\n    fontSize: 12,\n  };\n  return (\n    <div\n      style={{\n        ...row,\n        height: 40,\n        boxSizing: \"border-box\",\n        padding: \"0 20px\",\n        borderBottom: `1px solid ${c.hair}`,\n      }}\n    >\n      <PriorityIcon p={issue.p} c={c} />\n      <span style={{ width: 70, marginLeft: 14, color: c.sub, fontSize: 13 }}>\n        {issue.id}\n      </span>\n      <StatusIcon status={status} accent={accent} c={c} />\n      <span\n        style={{\n          marginLeft: 10,\n          flex: 1,\n          fontSize: 13,\n          fontWeight: 400,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        {issue.title}\n      </span>\n      <div style={{ ...row, gap: 6, color: c.sub }}>\n        {issue.labels.map((l) => (\n          <span key={l} style={chip}>\n            <span\n              style={{\n                width: 7,\n                height: 7,\n                borderRadius: 4,\n                background: LABEL_COLORS[l],\n              }}\n            />\n            {l}\n          </span>\n        ))}\n        {issue.project && (\n          <span style={chip}>\n            <Glyph name=\"cube\" size={12} color={c.sub} />\n            {issue.project}\n          </span>\n        )}\n      </div>\n      <div style={{ width: 20, marginLeft: 12 }}>\n        <Avatar name={issue.who || undefined} />\n      </div>\n      <span\n        style={{\n          width: 56,\n          marginLeft: 12,\n          textAlign: \"right\",\n          color: c.sub,\n          fontSize: 12.5,\n        }}\n      >\n        {issue.date}\n      </span>\n    </div>\n  );\n}\n\ninterface IssuesMockProps {\n  theme?: MockTheme;\n  accent?: string;\n  width?: number;\n  height?: number;\n  headerExtra?: React.ReactNode;\n}\n\nfunction IssuesMock({\n  theme = \"light\",\n  accent = \"#5E6AD2\",\n  width = MOCK_W,\n  height = MOCK_H,\n  headerExtra,\n}: IssuesMockProps) {\n  const c = MOCK_COLORS[theme];\n  const box = (size: number) => ({\n    ...center,\n    width: size,\n    height: size,\n    borderRadius: 7,\n    border: `1px solid ${c.border}`,\n  });\n  return (\n    <div\n      style={{\n        width,\n        height,\n        background: c.app,\n        display: \"flex\",\n        fontFamily,\n        color: c.text,\n        position: \"relative\",\n        overflow: \"hidden\",\n      }}\n    >\n      <div\n        style={{\n          width: SB,\n          flexShrink: 0,\n          display: \"flex\",\n          flexDirection: \"column\",\n        }}\n      >\n        <div\n          style={{\n            ...row,\n            height: 44,\n            marginTop: 4,\n            marginBottom: 4,\n            padding: \"0 12px 0 16px\",\n            gap: 8,\n          }}\n        >\n          <div\n            style={{\n              ...center,\n              width: 20,\n              height: 20,\n              borderRadius: 5,\n              background: accent,\n            }}\n          >\n            <svg viewBox=\"0 0 124.06 134.26\" height={11} aria-hidden>\n              <path fill=\"#fff\" d={REMOCN_R_PATH} />\n            </svg>\n          </div>\n          <span style={{ fontSize: 13.5, fontWeight: 400 }}>remocn</span>\n          <Glyph name=\"chevron\" size={12} color={c.sub} />\n          <div style={{ flex: 1 }} />\n          <Glyph name=\"search\" size={15} color={c.sub} />\n          <div style={{ ...box(28), background: c.panel }}>\n            <Glyph name=\"compose\" size={15} color={c.text} />\n          </div>\n        </div>\n        {NAV.split(\"|\").map((spec) => (\n          <NavItem key={spec} spec={spec} c={c} />\n        ))}\n        <div style={{ flex: 1 }} />\n        <div\n          style={{\n            margin: 10,\n            padding: \"10px 12px\",\n            borderRadius: 8,\n            border: `1px solid ${c.border}`,\n            background: c.panel,\n          }}\n        >\n          <div style={{ fontSize: 11.5, color: c.sub }}>What’s new</div>\n          <div style={{ fontSize: 12.5, fontWeight: 400, marginTop: 3 }}>\n            Captions in Studio\n          </div>\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: `8px 8px 8px ${SB}px`,\n          background: c.panel,\n          border: `1px solid ${c.border}`,\n          borderRadius: 10,\n          overflow: \"hidden\",\n        }}\n      >\n        <div\n          style={{\n            ...row,\n            height: 48,\n            boxSizing: \"border-box\",\n            padding: \"0 12px 0 14px\",\n            gap: 4,\n            borderBottom: `1px solid ${c.hair}`,\n          }}\n        >\n          {[\"All issues\", \"Active\", \"Backlog\"].map((t, i) => (\n            <span\n              key={t}\n              style={{\n                ...row,\n                height: 26,\n                padding: \"0 10px\",\n                borderRadius: 6,\n                fontSize: 12.5,\n                fontWeight: 400,\n                color: i === 0 ? c.text : c.sub,\n                background: i === 0 ? c.active : \"transparent\",\n              }}\n            >\n              {t}\n            </span>\n          ))}\n          <div style={{ flex: 1 }} />\n          {headerExtra}\n          {[\"filter\", \"display\"].map((g) => (\n            <div key={g} style={{ ...box(28), marginLeft: 4, borderRadius: 6 }}>\n              <Glyph name={g} size={15} color={c.sub} />\n            </div>\n          ))}\n        </div>\n        {MOCK_GROUPS.map((g) => (\n          <div key={g.name}>\n            <div\n              style={{\n                ...row,\n                height: 36,\n                boxSizing: \"border-box\",\n                padding: \"0 20px\",\n                gap: 10,\n                background: c.group,\n                fontSize: 13,\n              }}\n            >\n              <Glyph name=\"chevron\" size={12} color={c.faint} />\n              <StatusIcon status={g.status} accent={accent} c={c} />\n              <span style={{ fontWeight: 400 }}>{g.name}</span>\n              <span style={{ color: c.faint, flex: 1 }}>{g.count}</span>\n              <Glyph name=\"plus\" size={15} color={c.faint} />\n            </div>\n            {g.issues.map((issue) => (\n              <IssueRow\n                key={issue.id}\n                issue={issue}\n                status={g.status}\n                accent={accent}\n                c={c}\n              />\n            ))}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/theme-flip.tsx"
    }
  ],
  "type": "registry:component"
}
