{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bauhaus-build",
  "title": "Bauhaus Build",
  "description": "Flat Bauhaus circles, arcs, squares and bars tumble corner over corner on a hidden grid into an asymmetric frieze, then rebuild into two friezes around an open band for the title.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/bauhaus-build/index.tsx",
      "content": "\"use client\";\n\nimport { Easing, random, useCurrentFrame, useVideoConfig } from \"remotion\";\n\nexport type BauhausShape =\n  | \"circle\"\n  | \"square\"\n  | \"semicircle\"\n  | \"quarter\"\n  | \"bar\";\n\nexport type BauhausRole = \"ink\" | \"accent\" | \"secondary\" | \"tertiary\";\n\nexport type TumbleDirection = \"U\" | \"D\" | \"L\" | \"R\";\n\nexport type TumbleMove = `${TumbleDirection}${\"+\" | \"-\"}`;\n\nexport interface BauhausPose {\n  turn: number;\n  x: number;\n  y: number;\n}\n\nexport interface BauhausPoint {\n  x: number;\n  y: number;\n}\n\nexport interface BauhausFootprint {\n  x0: number;\n  y0: number;\n  x1: number;\n  y1: number;\n}\n\nexport interface BauhausPiece {\n  id: string;\n  shape: BauhausShape;\n  width: number;\n  height: number;\n  role: BauhausRole;\n  slot: BauhausPose;\n  enter: readonly TumbleMove[];\n  rebuild: readonly TumbleMove[];\n  cue: number;\n  after: readonly string[];\n}\n\nexport interface BauhausStep {\n  move: TumbleMove;\n  from: BauhausPose;\n  to: BauhausPose;\n  pivot: BauhausPoint;\n  spin: 1 | -1;\n}\n\nexport interface BauhausFrame {\n  x: number;\n  y: number;\n  rotation: number;\n  scale: number;\n  pivot: BauhausPoint | null;\n}\n\nexport interface BauhausPieceFrame extends BauhausFrame {\n  id: string;\n  shape: BauhausShape;\n  role: BauhausRole;\n  width: number;\n  height: number;\n  visible: boolean;\n}\n\nexport interface BauhausBuildProps {\n  scale?: number;\n  seed?: number;\n  inkColor?: string;\n  accentColor?: string;\n  secondaryColor?: string;\n  tertiaryColor?: string;\n  speed?: number;\n  loop?: boolean;\n  className?: string;\n}\n\ninterface TumbleRule {\n  direction: TumbleDirection;\n  spin: 1 | -1;\n  reverse: TumbleMove;\n}\n\nexport const bauhausBuildDefaults = {\n  scale: 1,\n  seed: 1,\n  inkColor: \"#1c1a17\",\n  accentColor: \"#e4572e\",\n  secondaryColor: \"#cbbc9f\",\n  tertiaryColor: \"#7e776b\",\n  speed: 1,\n  loop: false,\n};\n\nexport const bauhausBuildGrid = 12;\nexport const bauhausBuildBand = { top: 4, bottom: 8 };\nexport const bauhausBuildLength = 125;\nexport const bauhausBuildTimeline = {\n  lead: 22,\n  beat: 6,\n  rebuild: 90,\n  clear: 116,\n  duration: 150,\n};\n\nexport const bauhausBuildPieces: readonly BauhausPiece[] = [\n  {\n    id: \"hill\",\n    shape: \"quarter\",\n    width: 4,\n    height: 4,\n    role: \"ink\",\n    slot: { turn: 3, x: 0, y: 4 },\n    enter: [\"D-\", \"R+\"],\n    rebuild: [\"D-\"],\n    cue: 0,\n    after: [\"tile\"],\n  },\n  {\n    id: \"tile\",\n    shape: \"square\",\n    width: 1,\n    height: 1,\n    role: \"accent\",\n    slot: { turn: 0, x: 4, y: 7 },\n    enter: [\"U+\", \"U+\", \"U+\", \"U+\"],\n    rebuild: [\"D+\"],\n    cue: 3,\n    after: [\"pillar\"],\n  },\n  {\n    id: \"pillar\",\n    shape: \"bar\",\n    width: 4,\n    height: 1,\n    role: \"ink\",\n    slot: { turn: 1, x: 5, y: 4 },\n    enter: [\"R+\", \"R+\"],\n    rebuild: [\"U+\"],\n    cue: 6,\n    after: [\"sun\"],\n  },\n  {\n    id: \"sun\",\n    shape: \"circle\",\n    width: 4,\n    height: 4,\n    role: \"accent\",\n    slot: { turn: 0, x: 6, y: 4 },\n    enter: [\"U+\", \"U+\"],\n    rebuild: [\"U-\"],\n    cue: 9,\n    after: [],\n  },\n  {\n    id: \"dome\",\n    shape: \"semicircle\",\n    width: 2,\n    height: 2,\n    role: \"secondary\",\n    slot: { turn: 3, x: 1, y: 1 },\n    enter: [\"R+\", \"D-\"],\n    rebuild: [\"R+\"],\n    cue: 12,\n    after: [\"pillar\"],\n  },\n  {\n    id: \"post\",\n    shape: \"bar\",\n    width: 4,\n    height: 1,\n    role: \"tertiary\",\n    slot: { turn: 1, x: 11, y: 4 },\n    enter: [\"D+\", \"D+\"],\n    rebuild: [\"D-\"],\n    cue: 15,\n    after: [],\n  },\n  {\n    id: \"dot\",\n    shape: \"circle\",\n    width: 1,\n    height: 1,\n    role: \"ink\",\n    slot: { turn: 0, x: 11, y: 3 },\n    enter: [\"D-\", \"D-\", \"D-\", \"D-\"],\n    rebuild: [\"L-\"],\n    cue: 18,\n    after: [\"post\"],\n  },\n  {\n    id: \"block\",\n    shape: \"square\",\n    width: 2,\n    height: 2,\n    role: \"tertiary\",\n    slot: { turn: 0, x: 6, y: 10 },\n    enter: [\"U+\", \"U+\", \"U+\"],\n    rebuild: [\"L+\"],\n    cue: 21,\n    after: [\"sun\"],\n  },\n  {\n    id: \"fan\",\n    shape: \"quarter\",\n    width: 2,\n    height: 2,\n    role: \"secondary\",\n    slot: { turn: 2, x: 8, y: 10 },\n    enter: [\"L-\", \"L+\", \"L+\"],\n    rebuild: [\"R+\"],\n    cue: 24,\n    after: [\"block\"],\n  },\n];\n\nconst TURN = 5;\nconst FILL = 0.8;\nconst OVERSHOOT = 0.05;\nconst SETTLE = 0.5;\n\nconst MOVES: Record<TumbleMove, TumbleRule> = {\n  \"U+\": { direction: \"U\", spin: 1, reverse: \"D-\" },\n  \"U-\": { direction: \"U\", spin: -1, reverse: \"D+\" },\n  \"D+\": { direction: \"D\", spin: 1, reverse: \"U-\" },\n  \"D-\": { direction: \"D\", spin: -1, reverse: \"U+\" },\n  \"L+\": { direction: \"L\", spin: 1, reverse: \"R-\" },\n  \"L-\": { direction: \"L\", spin: -1, reverse: \"R+\" },\n  \"R+\": { direction: \"R\", spin: 1, reverse: \"L-\" },\n  \"R-\": { direction: \"R\", spin: -1, reverse: \"L+\" },\n};\n\nconst finite = (value: number, fallback: number) =>\n  Number.isFinite(value) ? value : fallback;\nconst clamp = (value: number, min = 0, max = 1) =>\n  Math.min(max, Math.max(min, value));\nconst swing = Easing.bezier(0.62, 0, 0.3, 1);\nconst settle = Easing.bezier(0.45, 0, 0.55, 1);\n\nfunction seedKey(seed: number) {\n  return Number.isFinite(seed) ? Math.round(seed) : 1;\n}\n\nexport function getBauhausFootprint(\n  piece: Pick<BauhausPiece, \"width\" | \"height\">,\n  pose: BauhausPose,\n): BauhausFootprint {\n  const odd = pose.turn % 2 !== 0;\n  return {\n    x0: pose.x,\n    y0: pose.y,\n    x1: pose.x + (odd ? piece.height : piece.width),\n    y1: pose.y + (odd ? piece.width : piece.height),\n  };\n}\n\nexport function getTumblePivot(\n  box: BauhausFootprint,\n  move: TumbleMove,\n): BauhausPoint {\n  const { direction, spin } = MOVES[move];\n  const clockwise = spin === 1;\n  switch (direction) {\n    case \"R\":\n      return { x: box.x1, y: clockwise ? box.y1 : box.y0 };\n    case \"L\":\n      return { x: box.x0, y: clockwise ? box.y0 : box.y1 };\n    case \"D\":\n      return { x: clockwise ? box.x0 : box.x1, y: box.y1 };\n    default:\n      return { x: clockwise ? box.x1 : box.x0, y: box.y0 };\n  }\n}\n\nconst quarterTurn = (\n  point: BauhausPoint,\n  pivot: BauhausPoint,\n  spin: 1 | -1,\n): BauhausPoint => ({\n  x: pivot.x - spin * (point.y - pivot.y),\n  y: pivot.y + spin * (point.x - pivot.x),\n});\n\nexport function tumble(\n  piece: Pick<BauhausPiece, \"width\" | \"height\">,\n  pose: BauhausPose,\n  move: TumbleMove,\n): BauhausStep {\n  const { spin } = MOVES[move];\n  const box = getBauhausFootprint(piece, pose);\n  const pivot = getTumblePivot(box, move);\n  const a = quarterTurn({ x: box.x0, y: box.y0 }, pivot, spin);\n  const b = quarterTurn({ x: box.x1, y: box.y1 }, pivot, spin);\n  return {\n    move,\n    from: pose,\n    to: {\n      turn: (pose.turn + spin + 4) % 4,\n      x: Math.min(a.x, b.x),\n      y: Math.min(a.y, b.y),\n    },\n    pivot,\n    spin,\n  };\n}\n\nexport function getBauhausPaths(piece: BauhausPiece) {\n  const walk = (from: BauhausPose, moves: readonly TumbleMove[]) => {\n    const steps: BauhausStep[] = [];\n    let pose = from;\n    for (const move of moves) {\n      const step = tumble(piece, pose, move);\n      steps.push(step);\n      pose = step.to;\n    }\n    return steps;\n  };\n  let start = piece.slot;\n  for (let index = piece.enter.length - 1; index >= 0; index--) {\n    start = tumble(piece, start, MOVES[piece.enter[index]].reverse).to;\n  }\n  const rebuild = walk(piece.slot, piece.rebuild);\n  return {\n    start,\n    enter: walk(start, piece.enter),\n    rebuild,\n    handoff: rebuild.at(-1)?.to ?? piece.slot,\n  };\n}\n\nfunction getRestOffset(\n  piece: Pick<BauhausPiece, \"width\" | \"height\">,\n  turn: number,\n): BauhausPoint {\n  switch (turn) {\n    case 1:\n      return { x: piece.height, y: 0 };\n    case 2:\n      return { x: piece.width, y: piece.height };\n    case 3:\n      return { x: 0, y: piece.width };\n    default:\n      return { x: 0, y: 0 };\n  }\n}\n\nexport function getBauhausPoseFrame(\n  piece: Pick<BauhausPiece, \"width\" | \"height\">,\n  pose: BauhausPose,\n): BauhausFrame {\n  const offset = getRestOffset(piece, pose.turn);\n  return {\n    x: pose.x + offset.x,\n    y: pose.y + offset.y,\n    rotation: pose.turn * 90,\n    scale: 1,\n    pivot: null,\n  };\n}\n\nexport function getBauhausStepFrame(\n  piece: Pick<BauhausPiece, \"width\" | \"height\">,\n  step: BauhausStep,\n  progress: number,\n  grow = 1,\n): BauhausFrame {\n  const rest = getBauhausPoseFrame(piece, step.from);\n  const angle = (step.spin * progress * Math.PI) / 2;\n  const cos = Math.cos(angle);\n  const sin = Math.sin(angle);\n  const dx = rest.x - step.pivot.x;\n  const dy = rest.y - step.pivot.y;\n  return {\n    x: step.pivot.x + grow * (dx * cos - dy * sin),\n    y: step.pivot.y + grow * (dx * sin + dy * cos),\n    rotation: rest.rotation + step.spin * progress * 90,\n    scale: grow,\n    pivot: step.pivot,\n  };\n}\n\nexport function getTumbleDuration(\n  piece: Pick<BauhausPiece, \"width\" | \"height\">,\n) {\n  return TURN * Math.sqrt(Math.max(piece.width, piece.height));\n}\n\nexport function getTumbleProgress(\n  elapsed: number,\n  duration: number,\n  landing: boolean,\n) {\n  const t = clamp(elapsed / duration);\n  if (!landing) return swing(t);\n  if (elapsed <= duration) return (1 + OVERSHOOT) * swing(t);\n  const back = clamp((elapsed - duration) / (duration * SETTLE));\n  return 1 + OVERSHOOT * (1 - settle(back));\n}\n\nexport function getUnfoldScale(progress: number) {\n  return 1 - (1 - clamp(progress)) ** 3;\n}\n\nfunction getPathFrame(\n  piece: BauhausPiece,\n  steps: readonly BauhausStep[],\n  elapsed: number,\n  unfold: boolean,\n): BauhausFrame {\n  const duration = getTumbleDuration(piece);\n  const last = steps.length - 1;\n  const index = Math.min(last, Math.max(0, Math.floor(elapsed / duration)));\n  const local = elapsed - index * duration;\n  const landing = index === last;\n  const step = steps[index];\n  if (landing && local >= duration * (1 + SETTLE)) {\n    return getBauhausPoseFrame(piece, step.to);\n  }\n  const grow = unfold && index === 0 ? getUnfoldScale(local / duration) : 1;\n  const progress = getTumbleProgress(local, duration, landing);\n  return getBauhausStepFrame(piece, step, progress, grow);\n}\n\nfunction getPieceMotion(\n  piece: BauhausPiece,\n  arrival: number,\n  time: number,\n): BauhausFrame {\n  const { lead, beat, rebuild } = bauhausBuildTimeline;\n  const paths = getBauhausPaths(piece);\n  const cue = rebuild + piece.cue;\n  if (time >= cue && paths.rebuild.length > 0) {\n    return getPathFrame(piece, paths.rebuild, time - cue, false);\n  }\n  if (paths.enter.length === 0) {\n    return getBauhausPoseFrame(piece, piece.slot);\n  }\n  const travel = paths.enter.length * getTumbleDuration(piece);\n  const start = lead + arrival * beat - travel;\n  if (time < start) {\n    return { ...getBauhausPoseFrame(piece, paths.start), scale: 0 };\n  }\n  return getPathFrame(piece, paths.enter, time - start, true);\n}\n\nexport function getBauhausBuildDuration({\n  speed = bauhausBuildDefaults.speed,\n  loop = bauhausBuildDefaults.loop,\n}: Pick<BauhausBuildProps, \"speed\" | \"loop\"> = {}) {\n  const rate = finite(speed, 1);\n  const frames = bauhausBuildTimeline.duration * (loop ? 2 : 1);\n  return rate <= 0 ? 1 : Math.max(1, Math.ceil(frames / rate));\n}\n\nexport function getBauhausBuildOrder(seed = bauhausBuildDefaults.seed) {\n  const key = seedKey(seed);\n  const rank = (piece: BauhausPiece) =>\n    random(`bauhaus-build-${key}-order-${piece.id}`);\n  const placed: BauhausPiece[] = [];\n  const waiting = [...bauhausBuildPieces];\n  while (waiting.length > 0) {\n    const ready = waiting.filter((piece) =>\n      piece.after.every((id) => placed.some((done) => done.id === id)),\n    );\n    const next = ready.reduce((best, piece) =>\n      rank(piece) < rank(best) ? piece : best,\n    );\n    placed.push(next);\n    waiting.splice(waiting.indexOf(next), 1);\n  }\n  return placed;\n}\n\nexport function getBauhausBuildVariant(seed = bauhausBuildDefaults.seed) {\n  const index = (((seedKey(seed) - 1) % 4) + 4) % 4;\n  return { flipX: index % 2 === 1, flipY: index >= 2 };\n}\n\nfunction getPoster(scale: number, width: number, height: number) {\n  const w = Math.max(1, finite(width, 1280));\n  const h = Math.max(1, finite(height, 720));\n  const size = Math.min(w, h) * FILL * clamp(finite(scale, 1), 0.5, 1.25);\n  return { w, h, size, originX: (w - size) / 2, originY: (h - size) / 2 };\n}\n\nexport function getBauhausBuildField({\n  scale = bauhausBuildDefaults.scale,\n  width = 1280,\n  height = 720,\n}: Pick<BauhausBuildProps, \"scale\"> & {\n  width?: number;\n  height?: number;\n} = {}) {\n  const poster = getPoster(scale, width, height);\n  const unit = poster.size / bauhausBuildGrid;\n  return {\n    x: 0,\n    y: poster.originY + bauhausBuildBand.top * unit,\n    width: poster.w,\n    height: (bauhausBuildBand.bottom - bauhausBuildBand.top) * unit,\n    unit,\n  };\n}\n\nexport function getBauhausBuildState(\n  time: number,\n  {\n    seed = bauhausBuildDefaults.seed,\n    scale = bauhausBuildDefaults.scale,\n    loop = bauhausBuildDefaults.loop,\n    width = 1280,\n    height = 720,\n  }: Pick<BauhausBuildProps, \"seed\" | \"scale\" | \"loop\"> & {\n    width?: number;\n    height?: number;\n  } = {},\n) {\n  const { size, originX, originY } = getPoster(scale, width, height);\n  const { duration } = bauhausBuildTimeline;\n  const elapsed = Math.max(0, finite(time, 0));\n  const folded = elapsed % (duration * 2);\n  const local = loop ? Math.min(folded, duration * 2 - folded) : elapsed;\n  const pieces = getBauhausBuildOrder(seed).map((piece, arrival) => {\n    const motion = getPieceMotion(piece, arrival, local);\n    const frame: BauhausPieceFrame = {\n      id: piece.id,\n      shape: piece.shape,\n      role: piece.role,\n      width: piece.width,\n      height: piece.height,\n      ...motion,\n      visible: motion.scale > 0.001,\n    };\n    return frame;\n  });\n  return {\n    time: local,\n    size,\n    unit: size / bauhausBuildGrid,\n    originX,\n    originY,\n    ...getBauhausBuildVariant(seed),\n    pieces,\n  };\n}\n\nexport function getBauhausShapePath(\n  shape: BauhausShape,\n  width: number,\n  height: number,\n) {\n  const r = width / 2;\n  switch (shape) {\n    case \"circle\":\n      return `M 0 ${r} A ${r} ${r} 0 1 1 ${width} ${r} A ${r} ${r} 0 1 1 0 ${r} Z`;\n    case \"semicircle\":\n      return `M 0 ${height} A ${r} ${r} 0 0 1 ${width} ${height} Z`;\n    case \"quarter\":\n      return `M 0 ${height} L 0 0 A ${width} ${height} 0 0 1 ${width} ${height} Z`;\n    default:\n      return `M 0 0 H ${width} V ${height} H 0 Z`;\n  }\n}\n\nexport function BauhausBuild({\n  scale = bauhausBuildDefaults.scale,\n  seed = bauhausBuildDefaults.seed,\n  inkColor = bauhausBuildDefaults.inkColor,\n  accentColor = bauhausBuildDefaults.accentColor,\n  secondaryColor = bauhausBuildDefaults.secondaryColor,\n  tertiaryColor = bauhausBuildDefaults.tertiaryColor,\n  speed = bauhausBuildDefaults.speed,\n  loop = bauhausBuildDefaults.loop,\n  className,\n}: BauhausBuildProps) {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const time = frame * (30 / fps) * Math.max(0, finite(speed, 1));\n  const state = getBauhausBuildState(time, {\n    seed,\n    scale,\n    loop,\n    width,\n    height,\n  });\n  const fills: Record<BauhausRole, string> = {\n    ink: inkColor,\n    accent: accentColor,\n    secondary: secondaryColor,\n    tertiary: tertiaryColor,\n  };\n  const mirror = `translate(${state.flipX ? width : 0} ${state.flipY ? height : 0}) scale(${state.flipX ? -1 : 1} ${state.flipY ? -1 : 1})`;\n\n  return (\n    <div\n      className={className}\n      style={{ position: \"absolute\", inset: 0, overflow: \"hidden\" }}\n    >\n      <svg\n        width=\"100%\"\n        height=\"100%\"\n        viewBox={`0 0 ${width} ${height}`}\n        role=\"img\"\n        aria-label=\"Geometric Bauhaus composition\"\n        style={{ display: \"block\" }}\n      >\n        <g transform={mirror}>\n          {state.pieces.map((piece) =>\n            piece.visible ? (\n              <path\n                key={piece.id}\n                d={getBauhausShapePath(piece.shape, piece.width, piece.height)}\n                fill={fills[piece.role]}\n                transform={`translate(${state.originX + piece.x * state.unit} ${state.originY + piece.y * state.unit}) rotate(${piece.rotation}) scale(${piece.scale * state.unit})`}\n              />\n            ) : null,\n          )}\n        </g>\n      </svg>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/bauhaus-build.tsx"
    }
  ],
  "type": "registry:component"
}
