{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flash-sweep",
  "title": "Flash Sweep",
  "description": "A tilted band of light sweeps across the frame and swaps one shot for the next.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/flash-sweep/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadSerif } from \"@remotion/google-fonts/InstrumentSerif\";\nimport { loadFont } from \"@remotion/google-fonts/Onest\";\nimport type React from \"react\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  Img,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: serif } = loadSerif(\"italic\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n/**\n * gradients.sh \"fluted\" palette: [#1d2a8f, #2f7bff, #ff7a3d, #ffe2c4]. It is\n * the ground of the closing statement shot (ribbed like fluted glass), the\n * product bar in the number shot and, on paper, the italic accent word.\n */\nconst FLUTED = [\"#1d2a8f\", \"#2f7bff\", \"#ff7a3d\", \"#ffe2c4\"] as const;\nconst flutedLinear = (deg: number) =>\n  `linear-gradient(${deg}deg, ${FLUTED[0]} 0%, ${FLUTED[1]} 48%, ${FLUTED[2]} 86%, ${FLUTED[3]} 108%)`;\n\n/* ------------------------------------------------------------------ */\n/* Built-in placeholder shots: abstract compositions in one palette.   */\n/* ------------------------------------------------------------------ */\n\ntype Tone = \"dark\" | \"graphite\" | \"light\" | \"accent\";\ntype Kind = \"title\" | \"pane\" | \"number\" | \"statement\";\ninterface SceneSpec {\n  kind: Kind;\n  tone: Tone;\n}\n\nfunction toRgb(color: string): [number, number, number] {\n  if (color.startsWith(\"rgb\")) {\n    const [r, g, b] = color.match(/[\\d.]+/g)?.map(Number) ?? [0, 0, 0];\n    return [r, g, b];\n  }\n  const h = color.replace(\"#\", \"\");\n  const v =\n    h.length === 3\n      ? h\n          .split(\"\")\n          .map((ch) => ch + ch)\n          .join(\"\")\n      : h.slice(0, 6);\n  const n = Number.parseInt(v, 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction mix(a: string, b: string, t: number) {\n  const x = toRgb(a);\n  const y = toRgb(b);\n  const c = x.map((v, i) => Math.round(v + (y[i] - v) * t));\n  return `rgb(${c[0]},${c[1]},${c[2]})`;\n}\n\nfunction tones(tone: Tone, accent: string) {\n  if (tone === \"light\") {\n    return {\n      bg: \"#F5F4F0\",\n      fg: \"#121214\",\n      muted: \"#6E6C67\",\n      hair: \"rgba(18,18,20,0.10)\",\n      surface: \"#FFFFFF\",\n      block: \"#E8E5DE\",\n      light: \"rgba(255,255,255,0.95)\",\n      shadow: \"rgba(40,36,30,0.16)\",\n      dot: accent,\n    };\n  }\n  if (tone === \"accent\") {\n    return {\n      // fluted glass: soft ribs over the palette, the warm end held back\n      bg: `repeating-linear-gradient(90deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0) 2.6%, rgba(0,0,0,0.09) 5.4%, rgba(255,255,255,0.06) 5.7%), linear-gradient(118deg, ${FLUTED[0]} 0%, ${FLUTED[1]} 52%, ${FLUTED[2]} 94%, ${FLUTED[3]} 120%)`,\n      fg: \"#F7F7F5\",\n      muted: \"rgba(247,247,245,0.86)\",\n      hair: \"rgba(255,255,255,0.22)\",\n      surface: mix(FLUTED[1], \"#0A0A0B\", 0.32),\n      block: \"rgba(255,255,255,0.16)\",\n      light: \"rgba(255,255,255,0.22)\",\n      shadow: \"rgba(0,0,0,0.35)\",\n      dot: \"#0A0A0B\",\n    };\n  }\n  const graphite = tone === \"graphite\";\n  return {\n    bg: graphite ? \"#17171A\" : \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    muted: graphite ? \"#97969C\" : \"#8B8A90\",\n    hair: \"rgba(255,255,255,0.10)\",\n    surface: graphite ? \"#1F1F23\" : \"#141416\",\n    block: graphite ? \"#2C2C31\" : \"#232326\",\n    light: graphite ? \"rgba(255,255,255,0.075)\" : \"rgba(255,255,255,0.065)\",\n    shadow: \"rgba(0,0,0,0.6)\",\n    dot: accent,\n  };\n}\n\ntype Colors = ReturnType<typeof tones>;\ninterface Ctx {\n  W: number;\n  H: number;\n  u: number;\n  c: Colors;\n  accent: string;\n}\n\n/**\n * The remocn.dev logo as outlines: the R mark (0.76em, on the baseline) and\n * \"emocn\" in Manrope 600 at -0.025em, 100 units per em, baseline at y = 0.\n * Glyph contours overlap (m, n, the e's bar), so they fill nonzero.\n */\nconst LOGO_R =\n  \"M 0.01 -75.54 C 0.01 -75.02 0.2 -74.42 0.62 -73.66 C 2.78 -69.75 7.61 -66.29 12.28 -65.3 C 12.99 -65.15 13.12 -65.15 29.09 -65.09 C 43.06 -65.04 45.3 -65.02 46.03 -64.92 C 50.88 -64.29 54.46 -62.03 56.26 -58.45 C 58.41 -54.17 58.04 -47.64 55.42 -43.69 C 53.58 -40.93 50.76 -39.21 47.13 -38.64 C 46.88 -38.6 39.73 -38.17 31.25 -37.68 C 14.05 -36.7 15.3 -36.81 13.69 -36.15 C 11.26 -35.16 8.4 -33.15 6.03 -30.78 C 2.75 -27.5 0.74 -23.97 0.14 -20.45 C 0.07 -20.04 0.05 -17.62 0.02 -9.96 L 0 0 5.38 0 L 10.76 0 10.78 -8.41 C 10.81 -17.54 10.8 -17.23 11.22 -18.8 C 12.33 -22.99 15.77 -25.76 20.63 -26.4 C 21.07 -26.45 22.31 -26.47 24.66 -26.45 C 28.37 -26.42 28.73 -26.38 30.13 -25.93 C 32.04 -25.3 33.78 -24 35.11 -22.18 C 35.39 -21.79 39.26 -17.21 43.7 -11.99 C 48.14 -6.78 51.87 -2.39 51.99 -2.23 C 52.12 -2.08 52.36 -1.65 52.54 -1.3 C 52.94 -0.52 53.27 -0.19 53.81 -0.07 C 54.06 -0.02 56.02 0 60.21 -0.02 L 66.24 -0.04 57.07 -11.99 C 52.03 -18.57 47.84 -24.05 47.77 -24.17 C 47.19 -25.12 47.36 -26.45 48.17 -27.32 C 48.56 -27.74 48.89 -27.92 49.85 -28.24 C 54.44 -29.74 58.96 -32.63 62.25 -36.17 C 68.26 -42.64 70.23 -50.91 67.61 -58.77 C 64.87 -67.05 57.7 -73.72 49.61 -75.53 C 47.51 -75.99 49.86 -75.95 23.69 -75.98 L 0.01 -76 0.01 -75.54 Z\";\n\nconst LOGO_EMOCN = [\n  // e\n  \"M105.2 1.5L105.2 1.5Q97.1 1.5 91 -2Q85 -5.6 81.6 -11.9Q78.2 -18.2 78.2 -26.6L78.2 -26.6Q78.2 -35.4 81.5 -41.9Q84.9 -48.4 90.8 -52Q96.8 -55.5 104.7 -55.5L104.7 -55.5Q112.9 -55.5 118.7 -51.7Q124.5 -47.9 127.4 -40.8Q130.3 -33.8 129.7 -24.1L129.7 -24.1L119.2 -24.1L119.2 -27.9Q119.1 -37.3 115.6 -41.8Q112.2 -46.3 105.1 -46.3L105.1 -46.3Q97.2 -46.3 93.3 -41.3Q89.3 -36.4 89.3 -27L89.3 -27Q89.3 -18.1 93.3 -13.2Q97.2 -8.3 104.7 -8.3L104.7 -8.3Q109.6 -8.3 113.1 -10.6Q116.7 -12.8 118.7 -16.9L118.7 -16.9L128.9 -13.7Q125.8 -6.5 119.4 -2.5Q113 1.5 105.2 1.5ZM124.5 -24.1L85.9 -24.1L85.9 -32.2L124.5 -32.2L124.5 -24.1Z\",\n  // m\n  \"M212 0L201.6 0L201.6 -33.6Q201.6 -39.5 198.6 -42.8Q195.6 -46.1 190.7 -46.1L190.7 -46.1Q187.7 -46.1 185.2 -44.7Q182.8 -43.3 181.2 -40.6Q179.7 -37.9 179.7 -33.8L179.7 -33.8L174.7 -36.3Q174.6 -42 177.1 -46.3Q179.6 -50.6 183.9 -53Q188.3 -55.4 193.7 -55.4L193.7 -55.4Q202.4 -55.4 207.3 -50.1Q212.1 -44.9 212.1 -36.1L212.1 -36.1L212 0ZM147.3 0L136.8 0L136.8 -54L146.1 -54L146.1 -38.4L147.3 -38.4L147.3 0ZM179.7 0L169.2 0L169.3 -33.5Q169.3 -39.4 166.3 -42.7Q163.3 -46.1 158.3 -46.1L158.3 -46.1Q153.3 -46.1 150.3 -42.7Q147.3 -39.3 147.3 -33.8L147.3 -33.8L142.3 -37Q142.3 -42.3 144.8 -46.4Q147.4 -50.6 151.8 -53Q156.1 -55.4 161.6 -55.4L161.6 -55.4Q167.3 -55.4 171.4 -52.9Q175.4 -50.5 177.6 -46.2Q179.7 -41.8 179.7 -36.1L179.7 -36.1L179.7 0Z\",\n  // o\n  \"M245.3 1.5L245.3 1.5Q237.2 1.5 231.3 -2.1Q225.3 -5.8 222.1 -12.2Q218.9 -18.7 218.9 -27.1L218.9 -27.1Q218.9 -35.5 222.2 -41.9Q225.5 -48.4 231.4 -51.9Q237.4 -55.5 245.3 -55.5L245.3 -55.5Q253.4 -55.5 259.4 -51.9Q265.3 -48.2 268.6 -41.8Q271.8 -35.4 271.8 -27.1L271.8 -27.1Q271.8 -18.6 268.6 -12.2Q265.3 -5.8 259.3 -2.1Q253.4 1.5 245.3 1.5ZM245.3 -8.3L245.3 -8.3Q253.1 -8.3 256.9 -13.6Q260.8 -18.8 260.8 -27.1L260.8 -27.1Q260.8 -35.5 256.9 -40.6Q253 -45.7 245.3 -45.7L245.3 -45.7Q240.1 -45.7 236.7 -43.3Q233.3 -40.9 231.6 -36.7Q230 -32.6 230 -27.1L230 -27.1Q230 -18.6 233.9 -13.5Q237.8 -8.3 245.3 -8.3Z\",\n  // c\n  \"M303.4 1.5L303.4 1.5Q295.1 1.5 289.3 -2.2Q283.5 -5.9 280.5 -12.4Q277.4 -18.8 277.3 -27L277.3 -27Q277.4 -35.4 280.5 -41.8Q283.7 -48.2 289.5 -51.9Q295.4 -55.5 303.6 -55.5L303.6 -55.5Q312.4 -55.5 318.7 -51.1Q325 -46.7 327 -39.1L327 -39.1L316.6 -36.1Q315 -40.6 311.5 -43.1Q308 -45.7 303.4 -45.7L303.4 -45.7Q298.3 -45.7 295 -43.2Q291.7 -40.8 290.1 -36.6Q288.5 -32.4 288.4 -27L288.4 -27Q288.5 -18.7 292.3 -13.5Q296 -8.3 303.4 -8.3L303.4 -8.3Q308.5 -8.3 311.6 -10.7Q314.8 -13 316.4 -17.4L316.4 -17.4L327 -14.9Q324.3 -7 318.3 -2.7Q312.1 1.5 303.4 1.5Z\",\n  // n\n  \"M383.2 0L372.6 0L372.6 -26.6Q372.6 -29.7 372.1 -33Q371.6 -36.4 370.1 -39.2Q368.6 -42.1 365.9 -43.8Q363.3 -45.6 358.9 -45.6L358.9 -45.6Q356.1 -45.6 353.6 -44.6Q351.1 -43.7 349.3 -41.7Q347.4 -39.6 346.3 -36.3Q345.3 -32.9 345.3 -28.1L345.3 -28.1L338.8 -30.6Q338.8 -37.9 341.5 -43.5Q344.3 -49.2 349.4 -52.3Q354.6 -55.5 361.8 -55.5L361.8 -55.5Q367.4 -55.5 371.3 -53.7Q375.1 -51.9 377.4 -48.9Q379.8 -46 381.1 -42.6Q382.3 -39.1 382.8 -35.8Q383.2 -32.5 383.2 -30L383.2 -30L383.2 0ZM345.3 0L334.6 0L334.6 -54L344 -54L344 -38.4L345.3 -38.4L345.3 0Z\",\n];\n/** Ink box of the outlined logo, in its own units; the R is `rW` wide. */\nconst LOGO = { x: 0, y: -76, w: 383.2, h: 77.5, rW: 70.23 } as const;\n\n/** The whole logo in one fill (a colour or a `url(#gradient)`). */\nfunction LogoPaths({ fill }: { fill: string }) {\n  return (\n    <g fill={fill}>\n      <path d={LOGO_R} />\n      {LOGO_EMOCN.map((d) => (\n        <path key={d.slice(0, 16)} d={d} />\n      ))}\n    </g>\n  );\n}\n\n/** The logo at a font size, ink box only (it sits on its own baseline). */\nfunction Logo({ size, color }: { size: number; color: string }) {\n  const k = size / 100;\n  return (\n    <svg\n      width={LOGO.w * k}\n      height={LOGO.h * k}\n      viewBox={`${LOGO.x} ${LOGO.y} ${LOGO.w} ${LOGO.h}`}\n      style={{ display: \"block\", flexShrink: 0, overflow: \"visible\" }}\n      aria-hidden\n    >\n      <LogoPaths fill={color} />\n    </svg>\n  );\n}\n\n/** The R mark alone, `height` tall. */\nfunction LogoR({ height, color }: { height: number; color: string }) {\n  return (\n    <svg\n      width={(height * LOGO.rW) / -LOGO.y}\n      height={height}\n      viewBox={`0 ${LOGO.y} ${LOGO.rW} ${-LOGO.y}`}\n      style={{ display: \"block\", flexShrink: 0 }}\n      aria-hidden\n    >\n      <path d={LOGO_R} fill={color} />\n    </svg>\n  );\n}\n\nconst box = (x: number, y: number, w: number, h: number) =>\n  ({\n    position: \"absolute\",\n    left: x,\n    top: y,\n    width: w,\n    height: h,\n  }) as const;\n\nconst displayType = (size: number): React.CSSProperties => ({\n  fontFamily: sans,\n  fontWeight: 500,\n  fontSize: size,\n  letterSpacing: \"-0.045em\",\n  lineHeight: 1,\n  fontVariantNumeric: \"tabular-nums\",\n});\n\nfunction TitleBody({ x }: { x: Ctx }) {\n  const { u, c } = x;\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        display: \"flex\",\n        flexDirection: \"column\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n        gap: 18 * u,\n      }}\n    >\n      <div\n        style={{\n          fontSize: 20 * u,\n          fontWeight: 400,\n          color: c.muted,\n          letterSpacing: \"0.01em\",\n        }}\n      >\n        Introducing\n      </div>\n      <div style={{ padding: `${10 * u}px 0 ${14 * u}px` }}>\n        <Logo size={150 * u} color={c.fg} />\n      </div>\n      <div\n        style={{ fontSize: 24 * u, color: c.muted, letterSpacing: \"-0.015em\" }}\n      >\n        Motion for the films you launch with.\n      </div>\n    </div>\n  );\n}\n\nfunction PaneBody({ x }: { x: Ctx }) {\n  const { W, H, u, c, accent } = x;\n  const p = { x: 0.08 * W, y: 0.1 * H, w: 0.84 * W, h: 0.8 * H };\n  const side = 0.27 * W;\n  const insp = 0.765 * W;\n  const bar = 0.075 * H;\n  const lightPane = c.surface === \"#FFFFFF\";\n  const tint = mix(accent, c.surface, lightPane ? 0.9 : 0.8);\n  const canvas = { x: 0.335 * W, y: 0.2 * H, w: 0.36 * W, h: 0.36 * H };\n  const rows = [0.62, 0.48, 0.7, 0.42, 0.56, 0.5];\n  const tracks: [number, number][][] = [\n    [\n      [0.3, 0.42],\n      [0.43, 0.62],\n    ],\n    [[0.34, 0.56]],\n    [\n      [0.29, 0.38],\n      [0.39, 0.52],\n      [0.53, 0.74],\n    ],\n  ];\n  return (\n    <>\n      <div\n        style={{\n          ...box(p.x, p.y, p.w, p.h),\n          background: c.surface,\n          borderRadius: 16 * u,\n          boxShadow: `0 0 0 1px ${c.hair}, 0 ${30 * u}px ${70 * u}px ${c.shadow}`,\n        }}\n      />\n      <div style={{ ...box(p.x, p.y + bar, p.w, 1), background: c.hair }} />\n      <div\n        style={{\n          position: \"absolute\",\n          left: p.x + 22 * u,\n          top: p.y,\n          height: bar,\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 9 * u,\n        }}\n      >\n        <Logo size={17 * u} color={c.fg} />\n        <div style={{ fontSize: 15 * u, color: c.muted, marginLeft: 6 * u }}>\n          / Launch film\n        </div>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          right: W - p.x - p.w + 18 * u,\n          top: p.y + bar / 2 - 15 * u,\n          height: 30 * u,\n          padding: `0 ${14 * u}px`,\n          borderRadius: 8 * u,\n          background: accent,\n          color: \"#FFFFFF\",\n          fontSize: 14 * u,\n          fontWeight: 500,\n          display: \"flex\",\n          alignItems: \"center\",\n        }}\n      >\n        Render\n      </div>\n      <div\n        style={{ ...box(side, p.y + bar, 1, p.h - bar), background: c.hair }}\n      />\n      <div\n        style={{ ...box(insp, p.y + bar, 1, p.h - bar), background: c.hair }}\n      />\n      {rows.map((w, i) => {\n        const y = p.y + bar + 26 * u + i * 0.062 * H;\n        const on = i === 1;\n        return (\n          <div key={w}>\n            {on ? (\n              <div\n                style={{\n                  ...box(p.x + 12 * u, y - 10 * u, side - p.x - 24 * u, 34 * u),\n                  background: tint,\n                  borderRadius: 8 * u,\n                }}\n              />\n            ) : null}\n            <div\n              style={{\n                ...box(p.x + 24 * u, y, 14 * u, 14 * u),\n                borderRadius: 4 * u,\n                background: on ? accent : c.block,\n              }}\n            />\n            <div\n              style={{\n                ...box(\n                  p.x + 48 * u,\n                  y + 3 * u,\n                  (side - p.x - 80 * u) * w,\n                  8 * u,\n                ),\n                borderRadius: 4 * u,\n                background: on ? mix(accent, c.surface, 0.55) : c.block,\n              }}\n            />\n          </div>\n        );\n      })}\n      {[0, 1, 2, 3, 4].map((i) => {\n        const y = p.y + bar + 28 * u + i * 0.075 * H;\n        return (\n          <div key={i}>\n            <div\n              style={{\n                ...box(insp + 20 * u, y, 0.04 * W, 7 * u),\n                borderRadius: 4 * u,\n                background: c.block,\n              }}\n            />\n            <div\n              style={{\n                ...box(insp + 20 * u, y + 16 * u, 0.155 * W - 40 * u, 22 * u),\n                borderRadius: 6 * u,\n                boxShadow: `inset 0 0 0 1px ${c.hair}`,\n              }}\n            />\n          </div>\n        );\n      })}\n      <div\n        style={{\n          ...box(side, canvas.y + canvas.h + 0.07 * H, insp - side, 1),\n          background: c.hair,\n        }}\n      />\n      {tracks.map((clips, ti) =>\n        clips.map(([a, b]) => (\n          <div\n            key={`${ti}-${a}`}\n            style={{\n              ...box(\n                a * W,\n                canvas.y + canvas.h + 0.1 * H + ti * 0.058 * H,\n                (b - a) * W - 4 * u,\n                0.042 * H,\n              ),\n              borderRadius: 6 * u,\n              background: ti === 1 ? accent : c.block,\n              opacity: ti === 1 ? 0.9 : 1,\n            }}\n          />\n        )),\n      )}\n      <div\n        style={{\n          ...box(0.47 * W, canvas.y + canvas.h + 0.085 * H, 2 * u, 0.2 * H),\n          background: c.fg,\n          borderRadius: u,\n        }}\n      />\n      <div\n        style={{\n          ...box(canvas.x, canvas.y, canvas.w, canvas.h),\n          borderRadius: 10 * u,\n          background: lightPane ? \"#0A0A0B\" : \"#F5F4F0\",\n          boxShadow: `inset 0 0 0 1px ${c.hair}`,\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          overflow: \"hidden\",\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            background: `radial-gradient(ellipse 80% 90% at 30% 0%, ${lightPane ? \"rgba(255,255,255,0.1)\" : \"rgba(255,255,255,0.9)\"}, transparent 70%)`,\n          }}\n        />\n        <LogoR height={48 * u} color={lightPane ? \"#F3F2EF\" : \"#121214\"} />\n      </div>\n    </>\n  );\n}\n\nfunction NumberBody({ x }: { x: Ctx }) {\n  const { W, H, u, c } = x;\n  const left = 0.08 * W;\n  return (\n    <>\n      <div\n        style={{\n          position: \"absolute\",\n          left,\n          top: 0.16 * H,\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 10 * u,\n          fontSize: 19 * u,\n          fontWeight: 500,\n          color: c.muted,\n        }}\n      >\n        <div\n          style={{\n            width: 9 * u,\n            height: 9 * u,\n            borderRadius: 9 * u,\n            background: c.dot,\n          }}\n        />\n        Median render time\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: left - 10 * u,\n          top: 0.27 * H,\n          ...displayType(250 * u),\n          fontWeight: 400,\n          letterSpacing: \"-0.055em\",\n        }}\n      >\n        4.8×\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left,\n          top: 0.27 * H + 268 * u,\n          width: 0.4 * W,\n          fontSize: 28 * u,\n          lineHeight: 1.25,\n          color: c.muted,\n          letterSpacing: \"-0.015em\",\n          textWrap: \"balance\",\n        }}\n      >\n        faster from script to final cut\n      </div>\n      {[\n        { label: \"Before\", w: 1, color: c.block },\n        { label: \"With remocn\", w: 1 / 4.8, color: flutedLinear(90) },\n      ].map((row, i) => (\n        <div\n          key={row.label}\n          style={{\n            position: \"absolute\",\n            left: 0.58 * W,\n            top: 0.4 * H + i * 0.13 * H,\n          }}\n        >\n          <div\n            style={{ fontSize: 18 * u, color: c.muted, marginBottom: 12 * u }}\n          >\n            {row.label}\n          </div>\n          <div\n            style={{\n              width: 0.32 * W * row.w,\n              height: 20 * u,\n              borderRadius: 10 * u,\n              background: row.color,\n              boxShadow: row.color.startsWith(\"linear\")\n                ? undefined\n                : `inset 0 0 0 1px ${c.hair}`,\n            }}\n          />\n        </div>\n      ))}\n    </>\n  );\n}\n\nfunction StatementBody({ x }: { x: Ctx }) {\n  const { W, H, u, c } = x;\n  return (\n    <>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0.08 * W,\n          top: 0.1 * H,\n          display: \"flex\",\n          alignItems: \"center\",\n          gap: 10 * u,\n        }}\n      >\n        <Logo size={24 * u} color={c.fg} />\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0.08 * W,\n          right: 0.08 * W,\n          top: 0.36 * H,\n          ...displayType(118 * u),\n          lineHeight: 1.02,\n          textWrap: \"balance\",\n        }}\n      >\n        Every frame,{\" \"}\n        <span\n          style={{\n            fontFamily: serif,\n            fontStyle: \"italic\",\n            fontWeight: 400,\n            letterSpacing: \"-0.01em\",\n            ...(c.bg === \"#F5F4F0\"\n              ? {\n                  backgroundImage: flutedLinear(100),\n                  WebkitBackgroundClip: \"text\",\n                  backgroundClip: \"text\",\n                  color: \"transparent\",\n                  paddingRight: \"0.08em\",\n                }\n              : null),\n          }}\n        >\n          yours.\n        </span>\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0.08 * W,\n          bottom: 0.1 * H,\n          fontSize: 20 * u,\n          color: c.muted,\n        }}\n      >\n        Copy it in. Change anything.\n      </div>\n      <div\n        style={{\n          position: \"absolute\",\n          right: 0.08 * W,\n          bottom: 0.1 * H,\n          fontSize: 20 * u,\n          color: c.muted,\n          fontVariantNumeric: \"tabular-nums\",\n        }}\n      >\n        remocn.dev\n      </div>\n    </>\n  );\n}\n\nconst BODIES: Record<Kind, (p: { x: Ctx }) => React.ReactElement> = {\n  title: TitleBody,\n  pane: PaneBody,\n  number: NumberBody,\n  statement: StatementBody,\n};\n\nconst LIGHT_AT: Record<Kind, string> = {\n  title: \"22% 6%\",\n  pane: \"50% -10%\",\n  number: \"86% 12%\",\n  statement: \"78% 100%\",\n};\n\n/** One full-frame placeholder shot. */\nfunction PlaceholderScene({\n  kind,\n  tone,\n  accent,\n}: SceneSpec & { accent: string }) {\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const c = tones(tone, accent);\n  const Body = BODIES[kind];\n  return (\n    <AbsoluteFill\n      style={{\n        background: c.bg,\n        color: c.fg,\n        fontFamily: sans,\n        overflow: \"hidden\",\n      }}\n    >\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 75% 85% at ${LIGHT_AT[kind]}, ${c.light}, transparent 72%)`,\n        }}\n      />\n      <Body x={{ W, H, u, c, accent }} />\n    </AbsoluteFill>\n  );\n}\n\nfunction ImageScene({ src }: { src: string }) {\n  return (\n    <AbsoluteFill style={{ background: \"#0A0A0B\" }}>\n      <Img\n        src={src}\n        style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\" }}\n      />\n    </AbsoluteFill>\n  );\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={11}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\n/** Resolves `scenes` / `from`+`to` / `images` / placeholders into a shot list. */\nfunction useShots({\n  scenes,\n  from,\n  to,\n  images,\n  fallback,\n  accent,\n}: {\n  scenes?: React.ReactNode[];\n  from?: React.ReactNode;\n  to?: React.ReactNode;\n  images?: string[];\n  fallback: SceneSpec[];\n  accent: string;\n}): React.ReactNode[] {\n  if (scenes && scenes.length >= 2) return scenes;\n  if (from !== undefined || to !== undefined) {\n    return [\n      from ?? <PlaceholderScene {...fallback[0]} accent={accent} />,\n      to ?? <PlaceholderScene {...fallback[1]} accent={accent} />,\n    ];\n  }\n  if (images && images.length >= 2) {\n    return images.map((src) => <ImageScene key={src} src={src} />);\n  }\n  return fallback.map((s) => (\n    <PlaceholderScene key={`${s.kind}-${s.tone}`} {...s} accent={accent} />\n  ));\n}\n\n/**\n * Splits the timeline into hold → cut → hold → cut … and returns which cut is\n * active (or the last one finished) plus its local frame.\n */\nfunction cutClock(t: number, cuts: number, hold: number, length: number) {\n  const k = Math.max(\n    0,\n    Math.min(cuts - 1, Math.floor((t - hold) / (hold + length))),\n  );\n  const local = t - hold - k * (hold + length);\n  return { k, local };\n}\n\n/* ------------------------------------------------------------------ */\n/* Flash sweep                                                         */\n/* ------------------------------------------------------------------ */\n\nexport interface FlashSweepProps {\n  /** Outgoing shot. */\n  from?: React.ReactNode;\n  /** Incoming shot. */\n  to?: React.ReactNode;\n  /** A chain of shots; every neighbouring pair gets one sweep. Wins over `from`/`to`. */\n  scenes?: React.ReactNode[];\n  /** Screenshots, used when no React shots are passed. */\n  images?: string[];\n  /** `light` burns through to white, `dark` passes a shadow over light shots. Follows `theme` by default. */\n  variant?: \"light\" | \"dark\";\n  /** Tilt of the band from vertical, in degrees. */\n  angle?: number;\n  /** Which way the band travels. */\n  direction?: \"right\" | \"left\";\n  /** Frames the band takes to cross the frame (8–14 reads best). */\n  duration?: number;\n  /** Frames each shot holds before the next sweep. */\n  hold?: number;\n  /** Colour of the light; defaults to a warm white (or near-black for `dark`). */\n  tint?: string;\n  accent?: string;\n  /** Playback multiplier. */\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\n/** Frames the incoming shot keeps settling after the peak. */\nconst SETTLE = 20;\n/** Frames the exposure takes to fall back after the peak. */\nconst BLOOM = 8;\nconst EASE_IN = Easing.bezier(0.55, 0, 1, 0.45);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_PASS = Easing.bezier(0.42, 0, 0.58, 1);\n\nconst DARK_SET: SceneSpec[] = [\n  { kind: \"title\", tone: \"dark\" },\n  { kind: \"pane\", tone: \"graphite\" },\n  { kind: \"number\", tone: \"dark\" },\n  { kind: \"statement\", tone: \"accent\" },\n];\nconst LIGHT_SET: SceneSpec[] = [\n  { kind: \"statement\", tone: \"light\" },\n  { kind: \"pane\", tone: \"light\" },\n  { kind: \"number\", tone: \"light\" },\n];\n\nfunction rgba(color: string, a: number) {\n  const [r, g, b] = toRgb(color);\n  return `rgba(${r},${g},${b},${a})`;\n}\n\n/** A soft band profile: wide shoulders, a bright core. */\nfunction bandGradient(color: string, strength: number) {\n  const stops: [number, number][] = [\n    [0, 0],\n    [18, 0.06],\n    [32, 0.28],\n    [42, 0.72],\n    [48, 0.96],\n    [50, 1],\n    [52, 0.96],\n    [58, 0.72],\n    [68, 0.28],\n    [82, 0.06],\n    [100, 0],\n  ];\n  return `linear-gradient(90deg, ${stops\n    .map(([at, a]) => `${rgba(color, a * strength)} ${at}%`)\n    .join(\", \")})`;\n}\n\nexport function FlashSweep({\n  from,\n  to,\n  scenes,\n  images,\n  variant,\n  angle = 22,\n  direction = \"right\",\n  duration = 12,\n  hold = 30,\n  tint,\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"dark\",\n}: FlashSweepProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const mode = variant ?? (theme === \"light\" ? \"dark\" : \"light\");\n  const light = mode === \"light\";\n  const color = tint ?? (light ? \"#FFFCF4\" : \"#0B0B0D\");\n  const D = Math.max(6, duration);\n  const shots = useShots({\n    scenes,\n    from,\n    to,\n    images,\n    fallback: theme === \"light\" ? LIGHT_SET : DARK_SET,\n    accent,\n  });\n  const cuts = shots.length - 1;\n  const { k, local } = cutClock(t, cuts, hold, D);\n  const peak = D / 2;\n  const swapped = local >= peak;\n  const j = swapped ? k + 1 : k;\n\n  // Exposure: climbs into the peak, holds one frame of full cover, falls fast.\n  const exposure = swapped\n    ? 1 - EASE_OUT(interpolate(local, [peak, peak + BLOOM], [0, 1], CLAMP))\n    : EASE_IN(interpolate(local, [peak * 0.2, peak], [0, 1], CLAMP));\n  const cover = swapped\n    ? interpolate(local, [peak, peak + 2.2], [1, 0], CLAMP)\n    : interpolate(local, [peak - 1.6, peak], [0, 1], CLAMP);\n\n  // Camera: push into the outgoing shot, the incoming one lands from a touch closer.\n  const push = swapped\n    ? 1 +\n      0.05 *\n        (1 - EASE_OUT(interpolate(local, [peak, peak + SETTLE], [0, 1], CLAMP)))\n    : 1 + 0.045 * EASE_IN(interpolate(local, [0, peak], [0, 1], CLAMP));\n  // Slow drift across a shot's whole life, so holds are never frozen.\n  const born = j === 0 ? -hold : hold + (j - 1) * (hold + D) + peak;\n  const drift =\n    1 + 0.02 * interpolate(t - born, [0, hold * 2 + D], [0, 1], CLAMP);\n\n  // Band travel along the sweep axis, measured from the frame centre.\n  const rad = (angle * Math.PI) / 180;\n  const reach = (W * Math.cos(rad) + H * Math.sin(Math.abs(rad))) / 2;\n  const bandW = Math.max(W, H) * 1.5;\n  const sweep = EASE_PASS(\n    interpolate(local, [-D * 0.15, D * 1.15], [0, 1], CLAMP),\n  );\n  const sgn = direction === \"left\" ? -1 : 1;\n  const travel = (lead: number) =>\n    sgn * (-(reach + bandW / 2) + (2 * reach + bandW) * sweep) * lead;\n  const inSweep = local > -D * 0.15 && local < D * 1.15;\n\n  const sceneFilter = light\n    ? `brightness(${1 + 0.7 * exposure}) saturate(${1 - 0.3 * exposure})`\n    : `brightness(${1 - 0.5 * exposure}) contrast(${1 + 0.1 * exposure})`;\n  const bloomBlur = 18 * u;\n\n  const band = (offset: number, w: number, strength: number, lead = 1) => {\n    const x = travel(lead) + offset * sgn;\n    return (\n      <div\n        style={{\n          position: \"absolute\",\n          left: W / 2 - w / 2,\n          top: H / 2 - H * 1.5,\n          width: w,\n          height: H * 3,\n          transform: `translateX(${x}px) rotate(${sgn * angle}deg)`,\n          background: bandGradient(color, strength),\n          mixBlendMode: light ? \"plus-lighter\" : \"multiply\",\n        }}\n      />\n    );\n  };\n\n  return (\n    <AbsoluteFill\n      style={{ background: light ? \"#0A0A0B\" : \"#F5F4F0\", overflow: \"hidden\" }}\n    >\n      <AbsoluteFill\n        style={{\n          transform: `scale(${push * drift})`,\n          filter: inSweep || exposure > 0.001 ? sceneFilter : undefined,\n        }}\n      >\n        {shots[j]}\n      </AbsoluteFill>\n      {light && exposure > 0.01 ? (\n        <AbsoluteFill\n          style={{\n            transform: `scale(${push * drift})`,\n            filter: `blur(${bloomBlur}px) brightness(1.6)`,\n            mixBlendMode: \"screen\",\n            opacity: 0.85 * exposure,\n          }}\n        >\n          {shots[j]}\n        </AbsoluteFill>\n      ) : null}\n      {inSweep ? (\n        <AbsoluteFill>\n          {band(0, bandW, 1)}\n          {/* Glass: thin reflections riding with the band at their own pace.\n              A shadow has no specular, so the dark pass gets one soft echo. */}\n          {light ? band(-0.32 * W, 26 * u, 0.55, 1.08) : null}\n          {light\n            ? band(0.3 * W, 70 * u, 0.3, 0.94)\n            : band(-0.34 * W, 220 * u, 0.3, 1.06)}\n        </AbsoluteFill>\n      ) : null}\n      {cover > 0 ? (\n        <AbsoluteFill\n          style={{\n            opacity: cover,\n            background: light\n              ? `radial-gradient(ellipse 85% 85% at ${50 - sgn * 12}% 40%, ${color}, ${mix(color, \"#D9D5CC\", 0.35)} 100%)`\n              : `radial-gradient(ellipse 85% 85% at ${50 - sgn * 12}% 40%, ${mix(color, \"#2A2A2E\", 0.5)}, ${color} 100%)`,\n          }}\n        />\n      ) : null}\n      <Grain opacity={theme === \"light\" ? 0.04 : 0.055} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/flash-sweep.tsx"
    }
  ],
  "type": "registry:component"
}
