{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "overnight",
  "title": "Overnight",
  "description": "A dated story line, \"The demo went up before the launch post did.\", on a yellow ground.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/overnight/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Afacad\";\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst afacad = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface StoryBeat {\n  slug: string;\n  line: string;\n}\n\nexport interface OvernightProps {\n  evening?: StoryBeat;\n  night?: StoryBeat;\n  morning?: StoryBeat;\n  accent?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst THEMES = {\n  dark: {\n    evening: \"#3A1F1A\",\n    eveningInk: \"#FBF3EA\",\n    night: \"#160B09\",\n    nightInk: \"#E9DCCF\",\n    morningInk: \"#3A1F1A\",\n    accent: \"#F2B544\",\n  },\n  light: {\n    evening: \"#FBF6EE\",\n    eveningInk: \"#3B2414\",\n    night: \"#2E1D14\",\n    nightInk: \"#F3E8DA\",\n    morningInk: \"#3B2414\",\n    accent: \"#B07A00\",\n  },\n} as const;\n\nconst DEFAULT_EVENING: StoryBeat = {\n  slug: \"Thursday, 6:00 PM\",\n  line: \"Priya’s launch was at nine the next morning.\",\n};\nconst DEFAULT_NIGHT: StoryBeat = {\n  slug: \"Friday, 12:50 AM\",\n  line: \"The demo video was still a blank file.\",\n};\nconst DEFAULT_MORNING: StoryBeat = {\n  slug: \"Friday, 8:55 AM\",\n  line: \"The demo went up before the launch post did.\",\n};\n\nconst LINE_TRACK = -0.022;\nconst SLUG_TRACK = 0.09;\nconst LH = 1.02;\nconst PX = 100;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_DUSK = Easing.bezier(0.7, 0, 0.25, 1);\nconst EASE_DAWN = Easing.bezier(0.6, 0, 0.2, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nfunction useWidths(strings: string[]) {\n  const key = strings.join(\"\\u0001\");\n  const [state, setState] = useState<{\n    key: string;\n    w: Record<string, number>;\n  } | null>(null);\n  const [handle] = useState(() => delayRender(\"overnight: measuring\"));\n  const released = useRef(false);\n  const ref = useRef(strings);\n  ref.current = strings;\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      afacad.waitUntilDone(),\n      document.fonts.load(`400 ${PX}px \"${afacad.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n        const w: Record<string, number> = {};\n        if (ctx) ctx.font = `400 ${PX}px \"${afacad.fontFamily}\"`;\n        for (const s of ref.current) {\n          w[s] = ctx ? ctx.measureText(s).width / PX : s.length * 0.45;\n        }\n        setState({ key, w });\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [key, handle]);\n  return state && state.key === key ? state.w : null;\n}\n\nfunction lineCount(ws: number[], space: number, max: number) {\n  let lines = 1;\n  let x = 0;\n  ws.forEach((w, i) => {\n    if (i > 0 && x + space + w > max) {\n      lines++;\n      x = w;\n    } else {\n      x += (i > 0 ? space : 0) + w;\n    }\n  });\n  return lines;\n}\n\nexport function Overnight({\n  evening = DEFAULT_EVENING,\n  night = DEFAULT_NIGHT,\n  morning = DEFAULT_MORNING,\n  accent,\n  speed = 1,\n  theme = \"dark\",\n}: OvernightProps) {\n  const frame = useCurrentFrame();\n  const t = frame * speed;\n  const { width: W, height: H, durationInFrames: T } = useVideoConfig();\n  const c = THEMES[theme];\n  const hi = accent ?? c.accent;\n  const morningBg =\n    theme === \"dark\"\n      ? hi\n      : `color-mix(in oklab, ${accent ?? THEMES.dark.accent} 42%, #FFFFFF)`;\n\n  const beats = [evening, night, morning];\n  const tokens = beats.map((b) => b.line.split(/\\s+/).filter(Boolean));\n  const strings = Array.from(new Set([\" \", ...tokens.flat()]));\n  const w = useWidths(strings);\n\n  if (!w) {\n    return (\n      <div style={{ position: \"absolute\", inset: 0, background: c.evening }} />\n    );\n  }\n\n  const portrait = H > W * 1.1;\n  const unit = Math.min(W, H);\n  const marginX = W * (portrait ? 0.08 : 0.07);\n  const marginB = H * (portrait ? 0.1 : 0.12);\n  const measure = W * (portrait ? 0.84 : 0.66);\n  const maxLines = portrait ? 5 : 3;\n  const space = w[\" \"] ?? 0.22;\n  const adv = (s: string) => (w[s] ?? 0) + LINE_TRACK * Array.from(s).length;\n\n  let F = portrait ? W * 0.13 : H * 0.15;\n  for (let k = 0; k < 60; k++) {\n    const ok = tokens.every(\n      (ws) => lineCount(ws.map(adv), space, measure / F) <= maxLines,\n    );\n    if (ok) break;\n    F *= 0.96;\n  }\n  const S = Math.max(14, Math.min(F * 0.27, unit * 0.034));\n\n  const k = Math.min(1.6, Math.max(0.75, T / 180));\n  const at = (f: number) => f * k;\n  const duskA = at(52);\n  const duskB = at(78);\n  const dawnA = at(118);\n  const dawnB = at(140);\n\n  const down = interpolate(t, [duskA, duskB], [0, 1], {\n    ...CLAMP,\n    easing: EASE_DUSK,\n  });\n  const up = interpolate(t, [dawnA, dawnB], [0, 1], {\n    ...CLAMP,\n    easing: EASE_DAWN,\n  });\n\n  const slugH = S * 1.2 + S * 1.1;\n  const blockH = (i: number) =>\n    slugH + lineCount(tokens[i].map(adv), space, measure / F) * F * LH;\n  const restTop = (i: number) => H - marginB - blockH(i);\n  const gap = unit * 0.04;\n  const duskEdge = H * down;\n  const dawnEdge = H - H * up;\n\n  const tops = [\n    Math.max(restTop(0), duskEdge + gap),\n    Math.min(restTop(1), dawnEdge - blockH(1) - gap),\n    restTop(2),\n  ];\n  const inks = [c.eveningInk, c.nightInk, c.morningInk];\n  const nightSlug = theme === \"light\" && !accent ? THEMES.dark.accent : hi;\n  const slugInks = [hi, nightSlug, c.morningInk];\n  const downAt = (f: number) =>\n    interpolate(f, [duskA, duskB], [0, 1], { ...CLAMP, easing: EASE_DUSK });\n  const upAt = (f: number) =>\n    interpolate(f, [dawnA, dawnB], [0, 1], { ...CLAMP, easing: EASE_DAWN });\n  const firstFrame = (ok: (f: number) => boolean, from: number, to: number) => {\n    for (let f = Math.floor(from); f <= Math.ceil(to); f++) {\n      if (ok(f)) return f;\n    }\n    return to;\n  };\n  const nightCover = firstFrame(\n    (f) => H * downAt(f) >= restTop(1) + blockH(1) + gap,\n    duskA,\n    duskB,\n  );\n  const dawnCover = firstFrame(\n    (f) => H - H * upAt(f) <= restTop(2) - gap,\n    dawnA,\n    dawnB,\n  );\n  const rise = (from: number, len: number) =>\n    interpolate(t, [from, from + len], [0, 1], {\n      ...CLAMP,\n      easing: EASE_OUT,\n    });\n  const enter = [\n    rise(4, 22),\n    rise(nightCover + 3, 18),\n    rise(dawnCover + 3, 18),\n  ];\n  const slugEnter = [rise(2, 16), rise(nightCover, 14), rise(dawnCover, 14)];\n\n  const text = (i: number) => {\n    if (tops[i] >= H || tops[i] + blockH(i) <= 0) return null;\n    const e = enter[i];\n    const se = slugEnter[i];\n    if (e <= 0.001 && se <= 0.001) return null;\n    return (\n      <div\n        key={`text${i}`}\n        style={{\n          position: \"absolute\",\n          left: marginX,\n          top: tops[i],\n          width: measure,\n        }}\n      >\n        <div\n          style={{\n            fontSize: S,\n            fontWeight: 500,\n            letterSpacing: `${SLUG_TRACK}em`,\n            textTransform: \"uppercase\",\n            color: slugInks[i],\n            lineHeight: 1.2,\n            marginBottom: S * 1.1,\n            whiteSpace: \"nowrap\",\n            opacity: se,\n            transform: `translateY(${((1 - se) * S).toFixed(2)}px)`,\n          }}\n        >\n          {beats[i].slug}\n        </div>\n        <div\n          style={{\n            fontSize: F,\n            fontWeight: 400,\n            letterSpacing: `${LINE_TRACK}em`,\n            lineHeight: LH,\n            color: inks[i],\n            textWrap: \"balance\",\n            opacity: e,\n            transform: `translateY(${((1 - e) * F * 0.32).toFixed(2)}px)`,\n          }}\n        >\n          {beats[i].line}\n        </div>\n      </div>\n    );\n  };\n\n  return (\n    <AbsoluteFill\n      style={{\n        background: c.evening,\n        overflow: \"hidden\",\n        fontFamily: afacad.fontFamily,\n      }}\n    >\n      {text(0)}\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: 0,\n          width: W,\n          height: duskEdge,\n          background: c.night,\n        }}\n      />\n      {text(1)}\n      <div\n        style={{\n          position: \"absolute\",\n          left: 0,\n          top: dawnEdge,\n          width: W,\n          height: H - dawnEdge,\n          background: morningBg,\n        }}\n      />\n      {text(2)}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/overnight.tsx"
    }
  ],
  "type": "registry:component"
}
